/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2006 Canonical Ltd
1740.3.1 by Jelmer Vernooij
Introduce and use CommitBuilder objects.
2
#
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.
7
#
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.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tests for repository commit builder."""
18
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
19
from copy import copy
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
20
import errno
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
21
import os
2804.4.2 by Alexander Belchenko
win32-specific fix for removing file/link/dir
22
import sys
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
23
24
from bzrlib import (
25
    errors,
26
    inventory,
27
    osutils,
28
    repository,
3668.5.1 by Jelmer Vernooij
Use NULL_REVISION rather than None for Repository.revision_tree().
29
    revision as _mod_revision,
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
30
    tests,
31
    )
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
32
from bzrlib.graph import Graph
3689.1.1 by John Arbash Meinel
Rename repository_implementations tests into per_repository tests
33
from bzrlib.tests.per_repository import test_repository
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
34
35
36
class TestCommitBuilder(test_repository.TestCaseWithRepository):
1740.3.3 by Jelmer Vernooij
Move storing directories and links to commit builder.
37
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
38
    def test_get_commit_builder(self):
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
39
        branch = self.make_branch('.')
40
        branch.repository.lock_write()
41
        builder = branch.repository.get_commit_builder(
42
            branch, [], branch.get_config())
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
43
        self.assertIsInstance(builder, repository.CommitBuilder)
2805.6.1 by Robert Collins
Set random_revid on CommitBuilder when a commit generated its own revision id.
44
        self.assertTrue(builder.random_revid)
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
45
        branch.repository.commit_write_group()
46
        branch.repository.unlock()
1740.3.3 by Jelmer Vernooij
Move storing directories and links to commit builder.
47
1910.2.6 by Aaron Bentley
Update for merge review, handle deprecations
48
    def record_root(self, builder, tree):
49
        if builder.record_root_entry is True:
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
50
            tree.lock_read()
51
            try:
52
                ie = tree.inventory.root
53
            finally:
54
                tree.unlock()
3668.5.1 by Jelmer Vernooij
Use NULL_REVISION rather than None for Repository.revision_tree().
55
            parent_tree = tree.branch.repository.revision_tree(
56
                              _mod_revision.NULL_REVISION)
1910.2.22 by Aaron Bentley
Make commits preserve root entry data
57
            parent_invs = []
2776.4.4 by Robert Collins
Move content summary generation outside of record_entry_contents.
58
            builder.record_entry_contents(ie, parent_invs, '', tree,
59
                tree.path_content_summary(''))
1731.1.33 by Aaron Bentley
Revert no-special-root changes
60
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
61
    def test_finish_inventory(self):
1740.3.7 by Jelmer Vernooij
Move committer, log, revprops, timestamp and timezone to CommitBuilder.
62
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
63
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
64
        try:
65
            builder = tree.branch.get_commit_builder([])
66
            self.record_root(builder, tree)
67
            builder.finish_inventory()
68
            tree.branch.repository.commit_write_group()
69
        finally:
70
            tree.unlock()
1740.3.3 by Jelmer Vernooij
Move storing directories and links to commit builder.
71
2749.3.1 by Jelmer Vernooij
Add CommitBuilder.abort().
72
    def test_abort(self):
73
        tree = self.make_branch_and_tree(".")
74
        tree.lock_write()
75
        try:
76
            builder = tree.branch.get_commit_builder([])
77
            self.record_root(builder, tree)
78
            builder.finish_inventory()
79
            builder.abort()
80
        finally:
81
            tree.unlock()
82
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
83
    def test_commit_message(self):
1740.3.7 by Jelmer Vernooij
Move committer, log, revprops, timestamp and timezone to CommitBuilder.
84
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
85
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
86
        try:
87
            builder = tree.branch.get_commit_builder([])
88
            self.record_root(builder, tree)
89
            builder.finish_inventory()
90
            rev_id = builder.commit('foo bar blah')
91
        finally:
92
            tree.unlock()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
93
        rev = tree.branch.repository.get_revision(rev_id)
94
        self.assertEqual('foo bar blah', rev.message)
95
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
96
    def test_commit_with_revision_id(self):
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
97
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
98
        tree.lock_write()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
99
        try:
2617.6.8 by Robert Collins
Review feedback and documentation.
100
            # use a unicode revision id to test more corner cases.
101
            # The repository layer is meant to handle this.
102
            revision_id = u'\xc8abc'.encode('utf8')
2150.2.2 by Robert Collins
Change the commit builder selected-revision-id test to use a unicode revision id where possible, leading to stricter testing of the hypothetical unicode revision id support in bzr.
103
            try:
2617.6.8 by Robert Collins
Review feedback and documentation.
104
                try:
105
                    builder = tree.branch.get_commit_builder([],
106
                        revision_id=revision_id)
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
107
                except errors.NonAsciiRevisionId:
2617.6.8 by Robert Collins
Review feedback and documentation.
108
                    revision_id = 'abc'
109
                    builder = tree.branch.get_commit_builder([],
110
                        revision_id=revision_id)
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
111
            except errors.CannotSetRevisionId:
2617.6.8 by Robert Collins
Review feedback and documentation.
112
                # This format doesn't support supplied revision ids
113
                return
2805.6.1 by Robert Collins
Set random_revid on CommitBuilder when a commit generated its own revision id.
114
            self.assertFalse(builder.random_revid)
2617.6.8 by Robert Collins
Review feedback and documentation.
115
            self.record_root(builder, tree)
116
            builder.finish_inventory()
117
            self.assertEqual(revision_id, builder.commit('foo bar'))
118
        finally:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
119
            tree.unlock()
2150.2.2 by Robert Collins
Change the commit builder selected-revision-id test to use a unicode revision id where possible, leading to stricter testing of the hypothetical unicode revision id support in bzr.
120
        self.assertTrue(tree.branch.repository.has_revision(revision_id))
121
        # the revision id must be set on the inventory when saving it. This
122
        # does not precisely test that - a repository that wants to can add it
123
        # on deserialisation, but thats all the current contract guarantees
124
        # anyway.
125
        self.assertEqual(revision_id,
126
            tree.branch.repository.get_inventory(revision_id).revision_id)
1740.3.8 by Jelmer Vernooij
Move make_revision() to commit builder.
127
2871.1.2 by Robert Collins
* ``CommitBuilder.record_entry_contents`` now requires the root entry of a
128
    def test_commit_without_root_errors(self):
1910.2.8 by Aaron Bentley
Fix commit_builder when root not passed to record_entry_contents
129
        tree = self.make_branch_and_tree(".")
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
130
        tree.lock_write()
131
        try:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
132
            self.build_tree(['foo'])
133
            tree.add('foo', 'foo-id')
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
134
            entry = tree.inventory['foo-id']
135
            builder = tree.branch.get_commit_builder([])
2871.1.2 by Robert Collins
* ``CommitBuilder.record_entry_contents`` now requires the root entry of a
136
            self.assertRaises(errors.RootMissing,
2776.4.4 by Robert Collins
Move content summary generation outside of record_entry_contents.
137
                builder.record_entry_contents, entry, [], 'foo', tree,
138
                    tree.path_content_summary('foo'))
2871.1.2 by Robert Collins
* ``CommitBuilder.record_entry_contents`` now requires the root entry of a
139
            builder.abort()
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
140
        finally:
141
            tree.unlock()
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
142
    
143
    def test_commit_unchanged_root(self):
144
        tree = self.make_branch_and_tree(".")
2903.2.3 by Martin Pool
CommitBuilder tests should expect the root to be in the delta iff it's changed in the commit
145
        old_revision_id = tree.commit('')
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
146
        tree.lock_write()
147
        parent_tree = tree.basis_tree()
148
        parent_tree.lock_read()
149
        self.addCleanup(parent_tree.unlock)
150
        builder = tree.branch.get_commit_builder([parent_tree.inventory])
151
        try:
152
            ie = inventory.make_entry('directory', '', None,
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
153
                    tree.get_root_id())
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
154
            delta, version_recorded, fs_hash = builder.record_entry_contents(
2776.4.13 by Robert Collins
Merge bzr.dev.
155
                ie, [parent_tree.inventory], '', tree,
2871.1.4 by Robert Collins
Merge bzr.dev.
156
                tree.path_content_summary(''))
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
157
            self.assertFalse(version_recorded)
2903.2.3 by Martin Pool
CommitBuilder tests should expect the root to be in the delta iff it's changed in the commit
158
            # if the repository format recorded a new root revision, that
159
            # should be in the delta
160
            got_new_revision = ie.revision != old_revision_id
161
            if got_new_revision:
162
                self.assertEqual(
163
                    ('', '', ie.file_id, ie),
164
                    delta)
165
            else:
166
                self.assertEqual(None, delta)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
167
            # Directories do not get hashed.
168
            self.assertEqual(None, fs_hash)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
169
            builder.abort()
170
        except:
171
            builder.abort()
172
            tree.unlock()
173
            raise
174
        else:
175
            tree.unlock()
1910.2.8 by Aaron Bentley
Fix commit_builder when root not passed to record_entry_contents
176
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
177
    def test_commit(self):
1740.3.8 by Jelmer Vernooij
Move make_revision() to commit builder.
178
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
179
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
180
        try:
181
            builder = tree.branch.get_commit_builder([])
182
            self.record_root(builder, tree)
183
            builder.finish_inventory()
184
            rev_id = builder.commit('foo bar')
185
        finally:
186
            tree.unlock()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
187
        self.assertNotEqual(None, rev_id)
188
        self.assertTrue(tree.branch.repository.has_revision(rev_id))
1757.1.2 by Robert Collins
Bugfix CommitBuilders recording of the inventory revision id.
189
        # the revision id must be set on the inventory when saving it. This does not
190
        # precisely test that - a repository that wants to can add it on deserialisation,
191
        # but thats all the current contract guarantees anyway.
192
        self.assertEqual(rev_id, tree.branch.repository.get_inventory(rev_id).revision_id)
2041.1.1 by John Arbash Meinel
Add a 'get_tree()' call that returns a RevisionTree for the newly committed tree
193
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
194
    def test_revision_tree(self):
2041.1.1 by John Arbash Meinel
Add a 'get_tree()' call that returns a RevisionTree for the newly committed tree
195
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
196
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
197
        try:
198
            builder = tree.branch.get_commit_builder([])
199
            self.record_root(builder, tree)
200
            builder.finish_inventory()
201
            rev_id = builder.commit('foo bar')
202
        finally:
203
            tree.unlock()
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
204
        rev_tree = builder.revision_tree()
2041.1.1 by John Arbash Meinel
Add a 'get_tree()' call that returns a RevisionTree for the newly committed tree
205
        # Just a couple simple tests to ensure that it actually follows
206
        # the RevisionTree api.
207
        self.assertEqual(rev_id, rev_tree.get_revision_id())
208
        self.assertEqual([], rev_tree.get_parent_ids())
2255.7.65 by Robert Collins
Split test_root_revision_entry into tree and repository portions.
209
210
    def test_root_entry_has_revision(self):
211
        # test the root revision created and put in the basis
212
        # has the right rev id.
213
        tree = self.make_branch_and_tree('.')
214
        rev_id = tree.commit('message')
215
        basis_tree = tree.basis_tree()
216
        basis_tree.lock_read()
217
        self.addCleanup(basis_tree.unlock)
218
        self.assertEqual(rev_id, basis_tree.inventory.root.revision)
219
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
220
    def _get_revtrees(self, tree, revision_ids):
2592.3.214 by Robert Collins
Merge bzr.dev.
221
        tree.lock_read()
222
        try:
223
            trees = list(tree.branch.repository.revision_trees(revision_ids))
224
            for _tree in trees:
225
                _tree.lock_read()
226
                self.addCleanup(_tree.unlock)
227
            return trees
228
        finally:
229
            tree.unlock()
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
230
231
    def test_last_modified_revision_after_commit_root_unchanged(self):
232
        # commiting without changing the root does not change the 
233
        # last modified except on non-rich-root-repositories.
234
        tree = self.make_branch_and_tree('.')
235
        rev1 = tree.commit('')
236
        rev2 = tree.commit('')
237
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
238
        self.assertEqual(rev1, tree1.inventory.root.revision)
239
        if tree.branch.repository.supports_rich_root():
240
            self.assertEqual(rev1, tree2.inventory.root.revision)
241
        else:
242
            self.assertEqual(rev2, tree2.inventory.root.revision)
243
244
    def _add_commit_check_unchanged(self, tree, name):
245
        tree.add([name], [name + 'id'])
246
        rev1 = tree.commit('')
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
247
        rev2 = self.mini_commit(tree, name, name, False, False)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
248
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
249
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
250
        self.assertEqual(rev1, tree2.inventory[name + 'id'].revision)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
251
        file_id = name + 'id'
252
        expected_graph = {}
253
        expected_graph[(file_id, rev1)] = ()
254
        self.assertFileGraph(expected_graph, tree, (file_id, rev1))
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
255
256
    def test_last_modified_revision_after_commit_dir_unchanged(self):
257
        # committing without changing a dir does not change the last modified.
258
        tree = self.make_branch_and_tree('.')
259
        self.build_tree(['dir/'])
260
        self._add_commit_check_unchanged(tree, 'dir')
261
262
    def test_last_modified_revision_after_commit_dir_contents_unchanged(self):
263
        # committing without changing a dir does not change the last modified
264
        # of the dir even the dirs contents are changed.
265
        tree = self.make_branch_and_tree('.')
266
        self.build_tree(['dir/'])
267
        tree.add(['dir'], ['dirid'])
268
        rev1 = tree.commit('')
269
        self.build_tree(['dir/content'])
270
        tree.add(['dir/content'], ['contentid'])
271
        rev2 = tree.commit('')
272
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
273
        self.assertEqual(rev1, tree1.inventory['dirid'].revision)
274
        self.assertEqual(rev1, tree2.inventory['dirid'].revision)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
275
        file_id = 'dirid'
276
        expected_graph = {}
277
        expected_graph[(file_id, rev1)] = ()
278
        self.assertFileGraph(expected_graph, tree, (file_id, rev1))
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
279
280
    def test_last_modified_revision_after_commit_file_unchanged(self):
281
        # committing without changing a file does not change the last modified.
282
        tree = self.make_branch_and_tree('.')
283
        self.build_tree(['file'])
284
        self._add_commit_check_unchanged(tree, 'file')
285
286
    def test_last_modified_revision_after_commit_link_unchanged(self):
287
        # committing without changing a link does not change the last modified.
288
        self.requireFeature(tests.SymlinkFeature)
289
        tree = self.make_branch_and_tree('.')
290
        os.symlink('target', 'link')
291
        self._add_commit_check_unchanged(tree, 'link')
292
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
293
    def _add_commit_renamed_check_changed(self, tree, name,
294
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
295
        def rename():
296
            tree.rename_one(name, 'new_' + name)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
297
        self._add_commit_change_check_changed(tree, name, rename,
298
            expect_fs_hash=expect_fs_hash)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
299
300
    def test_last_modified_revision_after_rename_dir_changes(self):
301
        # renaming a dir changes the last modified.
302
        tree = self.make_branch_and_tree('.')
303
        self.build_tree(['dir/'])
304
        self._add_commit_renamed_check_changed(tree, 'dir')
305
306
    def test_last_modified_revision_after_rename_file_changes(self):
307
        # renaming a file changes the last modified.
308
        tree = self.make_branch_and_tree('.')
309
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
310
        self._add_commit_renamed_check_changed(tree, 'file',
311
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
312
313
    def test_last_modified_revision_after_rename_link_changes(self):
314
        # renaming a link changes the last modified.
315
        self.requireFeature(tests.SymlinkFeature)
316
        tree = self.make_branch_and_tree('.')
317
        os.symlink('target', 'link')
318
        self._add_commit_renamed_check_changed(tree, 'link')
319
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
320
    def _add_commit_reparent_check_changed(self, tree, name,
321
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
322
        self.build_tree(['newparent/'])
323
        tree.add(['newparent'])
324
        def reparent():
325
            tree.rename_one(name, 'newparent/new_' + name)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
326
        self._add_commit_change_check_changed(tree, name, reparent,
327
            expect_fs_hash=expect_fs_hash)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
328
329
    def test_last_modified_revision_after_reparent_dir_changes(self):
330
        # reparenting a dir changes the last modified.
331
        tree = self.make_branch_and_tree('.')
332
        self.build_tree(['dir/'])
333
        self._add_commit_reparent_check_changed(tree, 'dir')
334
335
    def test_last_modified_revision_after_reparent_file_changes(self):
336
        # reparenting a file changes the last modified.
337
        tree = self.make_branch_and_tree('.')
338
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
339
        self._add_commit_reparent_check_changed(tree, 'file',
340
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
341
342
    def test_last_modified_revision_after_reparent_link_changes(self):
343
        # reparenting a link changes the last modified.
344
        self.requireFeature(tests.SymlinkFeature)
345
        tree = self.make_branch_and_tree('.')
346
        os.symlink('target', 'link')
347
        self._add_commit_reparent_check_changed(tree, 'link')
348
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
349
    def _add_commit_change_check_changed(self, tree, name, changer,
350
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
351
        tree.add([name], [name + 'id'])
352
        rev1 = tree.commit('')
353
        changer()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
354
        rev2 = self.mini_commit(tree, name, tree.id2path(name + 'id'),
355
            expect_fs_hash=expect_fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
356
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
357
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
358
        self.assertEqual(rev2, tree2.inventory[name + 'id'].revision)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
359
        file_id = name + 'id'
360
        expected_graph = {}
361
        expected_graph[(file_id, rev1)] = ()
362
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
363
        self.assertFileGraph(expected_graph, tree, (file_id, rev2))
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
364
365
    def mini_commit(self, tree, name, new_name, records_version=True,
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
366
        delta_against_basis=True, expect_fs_hash=False):
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
367
        """Perform a miniature commit looking for record entry results.
368
        
369
        :param tree: The tree to commit.
370
        :param name: The path in the basis tree of the tree being committed.
371
        :param new_name: The path in the tree being committed.
372
        :param records_version: True if the commit of new_name is expected to
373
            record a new version.
374
        :param delta_against_basis: True of the commit of new_name is expected
375
            to have a delta against the basis.
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
376
        :param expect_fs_hash: True or false to indicate whether we expect a
377
            file hash to be returned from the record_entry_contents call.
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
378
        """
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
379
        tree.lock_write()
380
        try:
381
            # mini manual commit here so we can check the return of
382
            # record_entry_contents.
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
383
            parent_ids = tree.get_parent_ids()
384
            builder = tree.branch.get_commit_builder(parent_ids)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
385
            parent_tree = tree.basis_tree()
386
            parent_tree.lock_read()
387
            self.addCleanup(parent_tree.unlock)
388
            parent_invs = [parent_tree.inventory]
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
389
            for parent_id in parent_ids[1:]:
390
                parent_invs.append(tree.branch.repository.revision_tree(
391
                    parent_id).inventory)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
392
            # root
393
            builder.record_entry_contents(
394
                inventory.make_entry('directory', '', None,
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
395
                    tree.get_root_id()), parent_invs, '', tree,
2776.4.13 by Robert Collins
Merge bzr.dev.
396
                    tree.path_content_summary(''))
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
397
            def commit_id(file_id):
398
                old_ie = tree.inventory[file_id]
399
                path = tree.id2path(file_id)
400
                ie = inventory.make_entry(tree.kind(file_id), old_ie.name,
401
                    old_ie.parent_id, file_id)
2776.4.13 by Robert Collins
Merge bzr.dev.
402
                return builder.record_entry_contents(ie, parent_invs, path,
403
                    tree, tree.path_content_summary(path))
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
404
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
405
            file_id = tree.path2id(new_name)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
406
            parent_id = tree.inventory[file_id].parent_id
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
407
            if parent_id != tree.get_root_id():
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
408
                commit_id(parent_id)
409
            # because a change of some sort is meant to have occurred,
410
            # recording the entry must return True.
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
411
            delta, version_recorded, fs_hash = commit_id(file_id)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
412
            if records_version:
413
                self.assertTrue(version_recorded)
414
            else:
415
                self.assertFalse(version_recorded)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
416
            if expect_fs_hash:
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
417
                tree_file_stat = tree.get_file_with_stat(file_id)
418
                tree_file_stat[0].close()
419
                self.assertEqual(2, len(fs_hash))
420
                self.assertEqual(tree.get_file_sha1(file_id), fs_hash[0])
421
                self.assertEqualStat(tree_file_stat[1], fs_hash[1])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
422
            else:
423
                self.assertEqual(None, fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
424
            new_entry = builder.new_inventory[file_id]
425
            if delta_against_basis:
426
                expected_delta = (name, new_name, file_id, new_entry)
427
            else:
428
                expected_delta = None
429
            self.assertEqual(expected_delta, delta)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
430
            builder.finish_inventory()
431
            rev2 = builder.commit('')
432
            tree.set_parent_ids([rev2])
433
        except:
434
            builder.abort()
435
            tree.unlock()
436
            raise
437
        else:
438
            tree.unlock()
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
439
        return rev2
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
440
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
441
    def assertFileGraph(self, expected_graph, tree, tip):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
442
        # all the changes that have occured should be in the ancestry
443
        # (closest to a public per-file graph API we have today)
444
        tree.lock_read()
445
        self.addCleanup(tree.unlock)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
446
        graph = dict(Graph(tree.branch.repository.texts).iter_ancestry([tip]))
447
        self.assertEqual(expected_graph, graph)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
448
449
    def test_last_modified_revision_after_content_file_changes(self):
450
        # altering a file changes the last modified.
451
        tree = self.make_branch_and_tree('.')
452
        self.build_tree(['file'])
453
        def change_file():
454
            tree.put_file_bytes_non_atomic('fileid', 'new content')
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
455
        self._add_commit_change_check_changed(tree, 'file', change_file,
456
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
457
458
    def test_last_modified_revision_after_content_link_changes(self):
459
        # changing a link changes the last modified.
460
        self.requireFeature(tests.SymlinkFeature)
461
        tree = self.make_branch_and_tree('.')
462
        os.symlink('target', 'link')
463
        def change_link():
464
            os.unlink('link')
465
            os.symlink('newtarget', 'link')
466
        self._add_commit_change_check_changed(tree, 'link', change_link)
467
468
    def _commit_sprout(self, tree, name):
469
        tree.add([name], [name + 'id'])
470
        rev_id = tree.commit('')
471
        return rev_id, tree.bzrdir.sprout('t2').open_workingtree()
472
473
    def _rename_in_tree(self, tree, name):
474
        tree.rename_one(name, 'new_' + name)
475
        return tree.commit('')
476
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
477
    def _commit_sprout_rename_merge(self, tree1, name, expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
478
        rev1, tree2 = self._commit_sprout(tree1, name)
479
        # change both sides equally
480
        rev2 = self._rename_in_tree(tree1, name)
481
        rev3 = self._rename_in_tree(tree2, name)
482
        tree1.merge_from_branch(tree2.branch)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
483
        rev4 = self.mini_commit(tree1, 'new_' + name, 'new_' + name,
484
            expect_fs_hash=expect_fs_hash)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
485
        tree3, = self._get_revtrees(tree1, [rev4])
486
        self.assertEqual(rev4, tree3.inventory[name + 'id'].revision)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
487
        file_id = name + 'id'
488
        expected_graph = {}
489
        expected_graph[(file_id, rev1)] = ()
490
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
491
        expected_graph[(file_id, rev3)] = ((file_id, rev1),)
492
        expected_graph[(file_id, rev4)] = ((file_id, rev2), (file_id, rev3),)
493
        self.assertFileGraph(expected_graph, tree1, (file_id, rev4))
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
494
495
    def test_last_modified_revision_after_merge_dir_changes(self):
496
        # merge a dir changes the last modified.
497
        tree1 = self.make_branch_and_tree('t1')
498
        self.build_tree(['t1/dir/'])
499
        self._commit_sprout_rename_merge(tree1, 'dir')
500
501
    def test_last_modified_revision_after_merge_file_changes(self):
502
        # merge a file changes the last modified.
503
        tree1 = self.make_branch_and_tree('t1')
504
        self.build_tree(['t1/file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
505
        self._commit_sprout_rename_merge(tree1, 'file', expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
506
507
    def test_last_modified_revision_after_merge_link_changes(self):
508
        # merge a link changes the last modified.
509
        self.requireFeature(tests.SymlinkFeature)
510
        tree1 = self.make_branch_and_tree('t1')
511
        os.symlink('target', 't1/link')
512
        self._commit_sprout_rename_merge(tree1, 'link')
513
514
    def _commit_sprout_rename_merge_converged(self, tree1, name):
515
        rev1, tree2 = self._commit_sprout(tree1, name)
516
        # change on the other side to merge back
517
        rev2 = self._rename_in_tree(tree2, name)
518
        tree1.merge_from_branch(tree2.branch)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
519
        rev3 = self.mini_commit(tree1, name, 'new_' + name, False)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
520
        tree3, = self._get_revtrees(tree1, [rev2])
521
        self.assertEqual(rev2, tree3.inventory[name + 'id'].revision)
3350.6.4 by Robert Collins
First cut at pluralised VersionedFiles. Some rather massive API incompatabilities, primarily because of the difficulty of coherence among competing stores.
522
        file_id = name + 'id'
523
        expected_graph = {}
524
        expected_graph[(file_id, rev1)] = ()
525
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
526
        self.assertFileGraph(expected_graph, tree1, (file_id, rev2))
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
527
528
    def test_last_modified_revision_after_converged_merge_dir_changes(self):
529
        # merge a dir changes the last modified.
530
        tree1 = self.make_branch_and_tree('t1')
531
        self.build_tree(['t1/dir/'])
532
        self._commit_sprout_rename_merge_converged(tree1, 'dir')
533
534
    def test_last_modified_revision_after_converged_merge_file_changes(self):
535
        # merge a file changes the last modified.
536
        tree1 = self.make_branch_and_tree('t1')
537
        self.build_tree(['t1/file'])
538
        self._commit_sprout_rename_merge_converged(tree1, 'file')
539
540
    def test_last_modified_revision_after_converged_merge_link_changes(self):
541
        # merge a link changes the last modified.
542
        self.requireFeature(tests.SymlinkFeature)
543
        tree1 = self.make_branch_and_tree('t1')
544
        os.symlink('target', 't1/link')
545
        self._commit_sprout_rename_merge_converged(tree1, 'link')
546
547
    def make_dir(self, name):
548
        self.build_tree([name + '/'])
549
550
    def make_file(self, name):
551
        self.build_tree([name])
552
553
    def make_link(self, name):
554
        self.requireFeature(tests.SymlinkFeature)
555
        os.symlink('target', name)
556
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
557
    def _check_kind_change(self, make_before, make_after, expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
558
        tree = self.make_branch_and_tree('.')
559
        path = 'name'
560
        make_before(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
561
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
562
        def change_kind():
2831.5.2 by Vincent Ladeuil
Review feedback.
563
            osutils.delete_any(path)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
564
            make_after(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
565
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
566
        self._add_commit_change_check_changed(tree, path, change_kind,
567
            expect_fs_hash=expect_fs_hash)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
568
569
    def test_last_modified_dir_file(self):
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
570
        self._check_kind_change(self.make_dir, self.make_file,
571
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
572
573
    def test_last_modified_dir_link(self):
574
        self._check_kind_change(self.make_dir, self.make_link)
575
576
    def test_last_modified_link_file(self):
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
577
        self._check_kind_change(self.make_link, self.make_file,
578
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
579
580
    def test_last_modified_link_dir(self):
581
        self._check_kind_change(self.make_link, self.make_dir)
582
583
    def test_last_modified_file_dir(self):
584
        self._check_kind_change(self.make_file, self.make_dir)
585
586
    def test_last_modified_file_link(self):
587
        self._check_kind_change(self.make_file, self.make_link)