/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)
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
165
                self.assertEqual(delta, builder.basis_delta[-1])
2903.2.3 by Martin Pool
CommitBuilder tests should expect the root to be in the delta iff it's changed in the commit
166
            else:
167
                self.assertEqual(None, delta)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
168
            # Directories do not get hashed.
169
            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
170
            builder.abort()
171
        except:
172
            builder.abort()
173
            tree.unlock()
174
            raise
175
        else:
176
            tree.unlock()
1910.2.8 by Aaron Bentley
Fix commit_builder when root not passed to record_entry_contents
177
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
178
    def test_commit(self):
1740.3.8 by Jelmer Vernooij
Move make_revision() to commit builder.
179
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
180
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
181
        try:
182
            builder = tree.branch.get_commit_builder([])
183
            self.record_root(builder, tree)
184
            builder.finish_inventory()
185
            rev_id = builder.commit('foo bar')
186
        finally:
187
            tree.unlock()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
188
        self.assertNotEqual(None, rev_id)
189
        self.assertTrue(tree.branch.repository.has_revision(rev_id))
1757.1.2 by Robert Collins
Bugfix CommitBuilders recording of the inventory revision id.
190
        # the revision id must be set on the inventory when saving it. This does not
191
        # precisely test that - a repository that wants to can add it on deserialisation,
192
        # but thats all the current contract guarantees anyway.
193
        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
194
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
195
    def test_record_delete(self):
196
        tree = self.make_branch_and_tree(".")
197
        self.build_tree(["foo"])
198
        tree.add(["foo"], ["foo-id"])
199
        rev_id = tree.commit("added foo")
200
        # Remove the inventory details for foo-id, because
201
        # record_entry_contents ends up copying root verbatim.
202
        tree.unversion(["foo-id"])
203
        tree.lock_write()
204
        try:
205
            basis = tree.branch.repository.revision_tree(rev_id)
206
            builder = tree.branch.get_commit_builder([rev_id])
207
            try:
208
                builder.recording_deletes()
209
                if builder.record_root_entry is True:
210
                    parent_invs = [basis.inventory]
211
                    del basis.inventory.root.children['foo']
212
                    builder.record_entry_contents(basis.inventory.root,
213
                        parent_invs, '', tree, tree.path_content_summary(''))
214
                builder.record_delete("foo", "foo-id")
215
                self.assertEqual(("foo", None, "foo-id", None),
216
                    builder.basis_delta[-1])
217
                builder.finish_inventory()
218
                rev_id2 = builder.commit('delete foo')
219
            except:
220
                tree.branch.repository.abort_write_group()
221
                raise
222
        finally:
223
            tree.unlock()
224
        rev_tree = builder.revision_tree()
225
        rev_tree.lock_read()
226
        self.addCleanup(rev_tree.unlock)
227
        self.assertFalse(rev_tree.path2id('foo'))
228
229
    def test_record_delete_without_notification(self):
230
        tree = self.make_branch_and_tree(".")
231
        self.build_tree(["foo"])
232
        tree.add(["foo"], ["foo-id"])
233
        rev_id = tree.commit("added foo")
234
        tree.lock_write()
235
        try:
236
            builder = tree.branch.get_commit_builder([rev_id])
237
            try:
238
                self.record_root(builder, tree)
239
                self.assertRaises(AssertionError,
240
                    builder.record_delete, "foo", "foo-id")
241
            finally:
242
                tree.branch.repository.abort_write_group()
243
        finally:
244
            tree.unlock()
245
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
246
    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
247
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
248
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
249
        try:
250
            builder = tree.branch.get_commit_builder([])
251
            self.record_root(builder, tree)
252
            builder.finish_inventory()
253
            rev_id = builder.commit('foo bar')
254
        finally:
255
            tree.unlock()
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
256
        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
257
        # Just a couple simple tests to ensure that it actually follows
258
        # the RevisionTree api.
259
        self.assertEqual(rev_id, rev_tree.get_revision_id())
260
        self.assertEqual([], rev_tree.get_parent_ids())
2255.7.65 by Robert Collins
Split test_root_revision_entry into tree and repository portions.
261
262
    def test_root_entry_has_revision(self):
263
        # test the root revision created and put in the basis
264
        # has the right rev id.
265
        tree = self.make_branch_and_tree('.')
266
        rev_id = tree.commit('message')
267
        basis_tree = tree.basis_tree()
268
        basis_tree.lock_read()
269
        self.addCleanup(basis_tree.unlock)
270
        self.assertEqual(rev_id, basis_tree.inventory.root.revision)
271
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
272
    def _get_revtrees(self, tree, revision_ids):
2592.3.214 by Robert Collins
Merge bzr.dev.
273
        tree.lock_read()
274
        try:
275
            trees = list(tree.branch.repository.revision_trees(revision_ids))
276
            for _tree in trees:
277
                _tree.lock_read()
278
                self.addCleanup(_tree.unlock)
279
            return trees
280
        finally:
281
            tree.unlock()
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
282
283
    def test_last_modified_revision_after_commit_root_unchanged(self):
284
        # commiting without changing the root does not change the 
285
        # last modified except on non-rich-root-repositories.
286
        tree = self.make_branch_and_tree('.')
287
        rev1 = tree.commit('')
288
        rev2 = tree.commit('')
289
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
290
        self.assertEqual(rev1, tree1.inventory.root.revision)
291
        if tree.branch.repository.supports_rich_root():
292
            self.assertEqual(rev1, tree2.inventory.root.revision)
293
        else:
294
            self.assertEqual(rev2, tree2.inventory.root.revision)
295
296
    def _add_commit_check_unchanged(self, tree, name):
297
        tree.add([name], [name + 'id'])
298
        rev1 = tree.commit('')
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
299
        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.
300
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
301
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
302
        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.
303
        file_id = name + 'id'
304
        expected_graph = {}
305
        expected_graph[(file_id, rev1)] = ()
306
        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.
307
308
    def test_last_modified_revision_after_commit_dir_unchanged(self):
309
        # committing without changing a dir does not change the last modified.
310
        tree = self.make_branch_and_tree('.')
311
        self.build_tree(['dir/'])
312
        self._add_commit_check_unchanged(tree, 'dir')
313
314
    def test_last_modified_revision_after_commit_dir_contents_unchanged(self):
315
        # committing without changing a dir does not change the last modified
316
        # of the dir even the dirs contents are changed.
317
        tree = self.make_branch_and_tree('.')
318
        self.build_tree(['dir/'])
319
        tree.add(['dir'], ['dirid'])
320
        rev1 = tree.commit('')
321
        self.build_tree(['dir/content'])
322
        tree.add(['dir/content'], ['contentid'])
323
        rev2 = tree.commit('')
324
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
325
        self.assertEqual(rev1, tree1.inventory['dirid'].revision)
326
        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.
327
        file_id = 'dirid'
328
        expected_graph = {}
329
        expected_graph[(file_id, rev1)] = ()
330
        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.
331
332
    def test_last_modified_revision_after_commit_file_unchanged(self):
333
        # committing without changing a file does not change the last modified.
334
        tree = self.make_branch_and_tree('.')
335
        self.build_tree(['file'])
336
        self._add_commit_check_unchanged(tree, 'file')
337
338
    def test_last_modified_revision_after_commit_link_unchanged(self):
339
        # committing without changing a link does not change the last modified.
340
        self.requireFeature(tests.SymlinkFeature)
341
        tree = self.make_branch_and_tree('.')
342
        os.symlink('target', 'link')
343
        self._add_commit_check_unchanged(tree, 'link')
344
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
345
    def _add_commit_renamed_check_changed(self, tree, name,
346
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
347
        def rename():
348
            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.
349
        self._add_commit_change_check_changed(tree, name, rename,
350
            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.
351
352
    def test_last_modified_revision_after_rename_dir_changes(self):
353
        # renaming a dir changes the last modified.
354
        tree = self.make_branch_and_tree('.')
355
        self.build_tree(['dir/'])
356
        self._add_commit_renamed_check_changed(tree, 'dir')
357
358
    def test_last_modified_revision_after_rename_file_changes(self):
359
        # renaming a file changes the last modified.
360
        tree = self.make_branch_and_tree('.')
361
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
362
        self._add_commit_renamed_check_changed(tree, 'file',
363
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
364
365
    def test_last_modified_revision_after_rename_link_changes(self):
366
        # renaming a link changes the last modified.
367
        self.requireFeature(tests.SymlinkFeature)
368
        tree = self.make_branch_and_tree('.')
369
        os.symlink('target', 'link')
370
        self._add_commit_renamed_check_changed(tree, 'link')
371
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
372
    def _add_commit_reparent_check_changed(self, tree, name,
373
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
374
        self.build_tree(['newparent/'])
375
        tree.add(['newparent'])
376
        def reparent():
377
            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.
378
        self._add_commit_change_check_changed(tree, name, reparent,
379
            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.
380
381
    def test_last_modified_revision_after_reparent_dir_changes(self):
382
        # reparenting a dir changes the last modified.
383
        tree = self.make_branch_and_tree('.')
384
        self.build_tree(['dir/'])
385
        self._add_commit_reparent_check_changed(tree, 'dir')
386
387
    def test_last_modified_revision_after_reparent_file_changes(self):
388
        # reparenting a file changes the last modified.
389
        tree = self.make_branch_and_tree('.')
390
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
391
        self._add_commit_reparent_check_changed(tree, 'file',
392
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
393
394
    def test_last_modified_revision_after_reparent_link_changes(self):
395
        # reparenting a link changes the last modified.
396
        self.requireFeature(tests.SymlinkFeature)
397
        tree = self.make_branch_and_tree('.')
398
        os.symlink('target', 'link')
399
        self._add_commit_reparent_check_changed(tree, 'link')
400
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
401
    def _add_commit_change_check_changed(self, tree, name, changer,
402
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
403
        tree.add([name], [name + 'id'])
404
        rev1 = tree.commit('')
405
        changer()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
406
        rev2 = self.mini_commit(tree, name, tree.id2path(name + 'id'),
407
            expect_fs_hash=expect_fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
408
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
409
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
410
        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.
411
        file_id = name + 'id'
412
        expected_graph = {}
413
        expected_graph[(file_id, rev1)] = ()
414
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
415
        self.assertFileGraph(expected_graph, tree, (file_id, rev2))
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
416
417
    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.
418
        delta_against_basis=True, expect_fs_hash=False):
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
419
        """Perform a miniature commit looking for record entry results.
420
        
421
        :param tree: The tree to commit.
422
        :param name: The path in the basis tree of the tree being committed.
423
        :param new_name: The path in the tree being committed.
424
        :param records_version: True if the commit of new_name is expected to
425
            record a new version.
426
        :param delta_against_basis: True of the commit of new_name is expected
427
            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.
428
        :param expect_fs_hash: True or false to indicate whether we expect a
429
            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
430
        """
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
431
        tree.lock_write()
432
        try:
433
            # mini manual commit here so we can check the return of
434
            # record_entry_contents.
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
435
            parent_ids = tree.get_parent_ids()
436
            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
437
            parent_tree = tree.basis_tree()
438
            parent_tree.lock_read()
439
            self.addCleanup(parent_tree.unlock)
440
            parent_invs = [parent_tree.inventory]
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
441
            for parent_id in parent_ids[1:]:
442
                parent_invs.append(tree.branch.repository.revision_tree(
443
                    parent_id).inventory)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
444
            # root
445
            builder.record_entry_contents(
446
                inventory.make_entry('directory', '', None,
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
447
                    tree.get_root_id()), parent_invs, '', tree,
2776.4.13 by Robert Collins
Merge bzr.dev.
448
                    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
449
            def commit_id(file_id):
450
                old_ie = tree.inventory[file_id]
451
                path = tree.id2path(file_id)
452
                ie = inventory.make_entry(tree.kind(file_id), old_ie.name,
453
                    old_ie.parent_id, file_id)
2776.4.13 by Robert Collins
Merge bzr.dev.
454
                return builder.record_entry_contents(ie, parent_invs, path,
455
                    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
456
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
457
            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
458
            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('')
459
            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
460
                commit_id(parent_id)
461
            # because a change of some sort is meant to have occurred,
462
            # 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.
463
            delta, version_recorded, fs_hash = commit_id(file_id)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
464
            if records_version:
465
                self.assertTrue(version_recorded)
466
            else:
467
                self.assertFalse(version_recorded)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
468
            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.
469
                tree_file_stat = tree.get_file_with_stat(file_id)
470
                tree_file_stat[0].close()
471
                self.assertEqual(2, len(fs_hash))
472
                self.assertEqual(tree.get_file_sha1(file_id), fs_hash[0])
473
                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.
474
            else:
475
                self.assertEqual(None, fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
476
            new_entry = builder.new_inventory[file_id]
477
            if delta_against_basis:
478
                expected_delta = (name, new_name, file_id, new_entry)
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
479
                self.assertEqual(expected_delta, builder.basis_delta[-1])
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
480
            else:
481
                expected_delta = None
482
            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
483
            builder.finish_inventory()
484
            rev2 = builder.commit('')
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
485
        except:
486
            builder.abort()
487
            tree.unlock()
488
            raise
489
        try:
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
490
            tree.set_parent_ids([rev2])
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
491
        finally:
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
492
            tree.unlock()
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
493
        return rev2
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
494
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.
495
    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.
496
        # all the changes that have occured should be in the ancestry
497
        # (closest to a public per-file graph API we have today)
498
        tree.lock_read()
499
        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.
500
        graph = dict(Graph(tree.branch.repository.texts).iter_ancestry([tip]))
501
        self.assertEqual(expected_graph, graph)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
502
503
    def test_last_modified_revision_after_content_file_changes(self):
504
        # altering a file changes the last modified.
505
        tree = self.make_branch_and_tree('.')
506
        self.build_tree(['file'])
507
        def change_file():
508
            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.
509
        self._add_commit_change_check_changed(tree, 'file', change_file,
510
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
511
512
    def test_last_modified_revision_after_content_link_changes(self):
513
        # changing a link changes the last modified.
514
        self.requireFeature(tests.SymlinkFeature)
515
        tree = self.make_branch_and_tree('.')
516
        os.symlink('target', 'link')
517
        def change_link():
518
            os.unlink('link')
519
            os.symlink('newtarget', 'link')
520
        self._add_commit_change_check_changed(tree, 'link', change_link)
521
522
    def _commit_sprout(self, tree, name):
523
        tree.add([name], [name + 'id'])
524
        rev_id = tree.commit('')
525
        return rev_id, tree.bzrdir.sprout('t2').open_workingtree()
526
527
    def _rename_in_tree(self, tree, name):
528
        tree.rename_one(name, 'new_' + name)
529
        return tree.commit('')
530
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
531
    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.
532
        rev1, tree2 = self._commit_sprout(tree1, name)
533
        # change both sides equally
534
        rev2 = self._rename_in_tree(tree1, name)
535
        rev3 = self._rename_in_tree(tree2, name)
536
        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.
537
        rev4 = self.mini_commit(tree1, 'new_' + name, 'new_' + name,
538
            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.
539
        tree3, = self._get_revtrees(tree1, [rev4])
540
        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.
541
        file_id = name + 'id'
542
        expected_graph = {}
543
        expected_graph[(file_id, rev1)] = ()
544
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
545
        expected_graph[(file_id, rev3)] = ((file_id, rev1),)
546
        expected_graph[(file_id, rev4)] = ((file_id, rev2), (file_id, rev3),)
547
        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.
548
549
    def test_last_modified_revision_after_merge_dir_changes(self):
550
        # merge a dir changes the last modified.
551
        tree1 = self.make_branch_and_tree('t1')
552
        self.build_tree(['t1/dir/'])
553
        self._commit_sprout_rename_merge(tree1, 'dir')
554
555
    def test_last_modified_revision_after_merge_file_changes(self):
556
        # merge a file changes the last modified.
557
        tree1 = self.make_branch_and_tree('t1')
558
        self.build_tree(['t1/file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
559
        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.
560
561
    def test_last_modified_revision_after_merge_link_changes(self):
562
        # merge a link changes the last modified.
563
        self.requireFeature(tests.SymlinkFeature)
564
        tree1 = self.make_branch_and_tree('t1')
565
        os.symlink('target', 't1/link')
566
        self._commit_sprout_rename_merge(tree1, 'link')
567
568
    def _commit_sprout_rename_merge_converged(self, tree1, name):
569
        rev1, tree2 = self._commit_sprout(tree1, name)
570
        # change on the other side to merge back
571
        rev2 = self._rename_in_tree(tree2, name)
572
        tree1.merge_from_branch(tree2.branch)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
573
        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.
574
        tree3, = self._get_revtrees(tree1, [rev2])
575
        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.
576
        file_id = name + 'id'
577
        expected_graph = {}
578
        expected_graph[(file_id, rev1)] = ()
579
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
580
        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.
581
582
    def test_last_modified_revision_after_converged_merge_dir_changes(self):
583
        # merge a dir changes the last modified.
584
        tree1 = self.make_branch_and_tree('t1')
585
        self.build_tree(['t1/dir/'])
586
        self._commit_sprout_rename_merge_converged(tree1, 'dir')
587
588
    def test_last_modified_revision_after_converged_merge_file_changes(self):
589
        # merge a file changes the last modified.
590
        tree1 = self.make_branch_and_tree('t1')
591
        self.build_tree(['t1/file'])
592
        self._commit_sprout_rename_merge_converged(tree1, 'file')
593
594
    def test_last_modified_revision_after_converged_merge_link_changes(self):
595
        # merge a link changes the last modified.
596
        self.requireFeature(tests.SymlinkFeature)
597
        tree1 = self.make_branch_and_tree('t1')
598
        os.symlink('target', 't1/link')
599
        self._commit_sprout_rename_merge_converged(tree1, 'link')
600
601
    def make_dir(self, name):
602
        self.build_tree([name + '/'])
603
604
    def make_file(self, name):
605
        self.build_tree([name])
606
607
    def make_link(self, name):
608
        self.requireFeature(tests.SymlinkFeature)
609
        os.symlink('target', name)
610
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
611
    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.
612
        tree = self.make_branch_and_tree('.')
613
        path = 'name'
614
        make_before(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
615
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
616
        def change_kind():
2831.5.2 by Vincent Ladeuil
Review feedback.
617
            osutils.delete_any(path)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
618
            make_after(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
619
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
620
        self._add_commit_change_check_changed(tree, path, change_kind,
621
            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.
622
623
    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.
624
        self._check_kind_change(self.make_dir, self.make_file,
625
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
626
627
    def test_last_modified_dir_link(self):
628
        self._check_kind_change(self.make_dir, self.make_link)
629
630
    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.
631
        self._check_kind_change(self.make_link, self.make_file,
632
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
633
634
    def test_last_modified_link_dir(self):
635
        self._check_kind_change(self.make_link, self.make_dir)
636
637
    def test_last_modified_file_dir(self):
638
        self._check_kind_change(self.make_file, self.make_dir)
639
640
    def test_last_modified_file_link(self):
641
        self._check_kind_change(self.make_file, self.make_link)