/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()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
142
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
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)
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
150
        builder = tree.branch.get_commit_builder([old_revision_id])
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
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:
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
162
                self.assertEqual(('', '', ie.file_id, ie), delta)
163
                # The delta should be tracked
164
                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
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
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
194
    def test_get_basis_delta(self):
195
        tree = self.make_branch_and_tree(".")
196
        self.build_tree(["foo"])
197
        tree.add(["foo"], ["foo-id"])
198
        old_revision_id = tree.commit("added foo")
199
        tree.lock_write()
200
        try:
201
            self.build_tree(['bar'])
202
            tree.add(['bar'], ['bar-id'])
203
            basis = tree.branch.repository.revision_tree(old_revision_id)
204
            basis.lock_read()
205
            self.addCleanup(basis.unlock)
206
            builder = tree.branch.get_commit_builder([old_revision_id])
207
            total_delta = []
208
            try:
209
                parent_invs = [basis.inventory]
210
                builder.will_record_deletes()
211
                if builder.record_root_entry:
212
                    ie = basis.inventory.root.copy()
213
                    delta, _, _ = builder.record_entry_contents(ie, parent_invs,
214
                        '', tree, tree.path_content_summary(''))
215
                    if delta is not None:
216
                        total_delta.append(delta)
217
                delta = builder.record_delete("foo", "foo-id")
218
                total_delta.append(delta)
219
                new_bar = inventory.make_entry('file', 'bar',
220
                    parent_id=tree.get_root_id(), file_id='bar-id')
221
                delta, _, _ = builder.record_entry_contents(new_bar, parent_invs,
222
                    'bar', tree, tree.path_content_summary('bar'))
223
                total_delta.append(delta)
224
                # All actions should have been recorded in the basis_delta
225
                self.assertEqual(total_delta, builder.get_basis_delta())
226
                builder.finish_inventory()
227
                builder.commit('delete foo, add bar')
228
            except:
229
                tree.branch.repository.abort_write_group()
230
                raise
231
        finally:
232
            tree.unlock()
233
234
    def test_get_basis_delta_without_notification(self):
235
        tree = self.make_branch_and_tree(".")
236
        old_revision_id = tree.commit('')
237
        tree.lock_write()
238
        try:
239
            parent_tree = tree.basis_tree()
240
            parent_tree.lock_read()
241
            self.addCleanup(parent_tree.unlock)
242
            builder = tree.branch.get_commit_builder([old_revision_id])
243
            # It is an error to expect builder.get_basis_delta() to be correct,
244
            # if you have not also called will_record_deletes() to indicate you
245
            # will be calling record_delete() when appropriate
246
            self.assertRaises(AssertionError, builder.get_basis_delta)
247
            tree.branch.repository.abort_write_group()
248
        finally:
249
            tree.unlock()
250
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
251
    def test_record_delete(self):
252
        tree = self.make_branch_and_tree(".")
253
        self.build_tree(["foo"])
254
        tree.add(["foo"], ["foo-id"])
255
        rev_id = tree.commit("added foo")
256
        # Remove the inventory details for foo-id, because
257
        # record_entry_contents ends up copying root verbatim.
258
        tree.unversion(["foo-id"])
259
        tree.lock_write()
260
        try:
261
            basis = tree.branch.repository.revision_tree(rev_id)
262
            builder = tree.branch.get_commit_builder([rev_id])
263
            try:
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
264
                builder.will_record_deletes()
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
265
                if builder.record_root_entry is True:
266
                    parent_invs = [basis.inventory]
267
                    del basis.inventory.root.children['foo']
268
                    builder.record_entry_contents(basis.inventory.root,
269
                        parent_invs, '', tree, tree.path_content_summary(''))
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
270
                # the delta should be returned, and recorded in _basis_delta
271
                delta = builder.record_delete("foo", "foo-id")
272
                self.assertEqual(("foo", None, "foo-id", None), delta)
273
                self.assertEqual(delta, builder._basis_delta[-1])
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
274
                builder.finish_inventory()
275
                rev_id2 = builder.commit('delete foo')
276
            except:
277
                tree.branch.repository.abort_write_group()
278
                raise
279
        finally:
280
            tree.unlock()
281
        rev_tree = builder.revision_tree()
282
        rev_tree.lock_read()
283
        self.addCleanup(rev_tree.unlock)
284
        self.assertFalse(rev_tree.path2id('foo'))
285
286
    def test_record_delete_without_notification(self):
287
        tree = self.make_branch_and_tree(".")
288
        self.build_tree(["foo"])
289
        tree.add(["foo"], ["foo-id"])
290
        rev_id = tree.commit("added foo")
291
        tree.lock_write()
292
        try:
293
            builder = tree.branch.get_commit_builder([rev_id])
294
            try:
295
                self.record_root(builder, tree)
296
                self.assertRaises(AssertionError,
297
                    builder.record_delete, "foo", "foo-id")
298
            finally:
299
                tree.branch.repository.abort_write_group()
300
        finally:
301
            tree.unlock()
302
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
303
    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
304
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
305
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
306
        try:
307
            builder = tree.branch.get_commit_builder([])
308
            self.record_root(builder, tree)
309
            builder.finish_inventory()
310
            rev_id = builder.commit('foo bar')
311
        finally:
312
            tree.unlock()
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
313
        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
314
        # Just a couple simple tests to ensure that it actually follows
315
        # the RevisionTree api.
316
        self.assertEqual(rev_id, rev_tree.get_revision_id())
317
        self.assertEqual([], rev_tree.get_parent_ids())
2255.7.65 by Robert Collins
Split test_root_revision_entry into tree and repository portions.
318
319
    def test_root_entry_has_revision(self):
320
        # test the root revision created and put in the basis
321
        # has the right rev id.
322
        tree = self.make_branch_and_tree('.')
323
        rev_id = tree.commit('message')
324
        basis_tree = tree.basis_tree()
325
        basis_tree.lock_read()
326
        self.addCleanup(basis_tree.unlock)
327
        self.assertEqual(rev_id, basis_tree.inventory.root.revision)
328
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
329
    def _get_revtrees(self, tree, revision_ids):
2592.3.214 by Robert Collins
Merge bzr.dev.
330
        tree.lock_read()
331
        try:
332
            trees = list(tree.branch.repository.revision_trees(revision_ids))
333
            for _tree in trees:
334
                _tree.lock_read()
335
                self.addCleanup(_tree.unlock)
336
            return trees
337
        finally:
338
            tree.unlock()
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
339
340
    def test_last_modified_revision_after_commit_root_unchanged(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
341
        # commiting without changing the root does not change the
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
342
        # last modified except on non-rich-root-repositories.
343
        tree = self.make_branch_and_tree('.')
344
        rev1 = tree.commit('')
345
        rev2 = tree.commit('')
346
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
347
        self.assertEqual(rev1, tree1.inventory.root.revision)
348
        if tree.branch.repository.supports_rich_root():
349
            self.assertEqual(rev1, tree2.inventory.root.revision)
350
        else:
351
            self.assertEqual(rev2, tree2.inventory.root.revision)
352
353
    def _add_commit_check_unchanged(self, tree, name):
354
        tree.add([name], [name + 'id'])
355
        rev1 = tree.commit('')
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
356
        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.
357
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
358
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
359
        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.
360
        file_id = name + 'id'
361
        expected_graph = {}
362
        expected_graph[(file_id, rev1)] = ()
363
        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.
364
365
    def test_last_modified_revision_after_commit_dir_unchanged(self):
366
        # committing without changing a dir does not change the last modified.
367
        tree = self.make_branch_and_tree('.')
368
        self.build_tree(['dir/'])
369
        self._add_commit_check_unchanged(tree, 'dir')
370
371
    def test_last_modified_revision_after_commit_dir_contents_unchanged(self):
372
        # committing without changing a dir does not change the last modified
373
        # of the dir even the dirs contents are changed.
374
        tree = self.make_branch_and_tree('.')
375
        self.build_tree(['dir/'])
376
        tree.add(['dir'], ['dirid'])
377
        rev1 = tree.commit('')
378
        self.build_tree(['dir/content'])
379
        tree.add(['dir/content'], ['contentid'])
380
        rev2 = tree.commit('')
381
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
382
        self.assertEqual(rev1, tree1.inventory['dirid'].revision)
383
        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.
384
        file_id = 'dirid'
385
        expected_graph = {}
386
        expected_graph[(file_id, rev1)] = ()
387
        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.
388
389
    def test_last_modified_revision_after_commit_file_unchanged(self):
390
        # committing without changing a file does not change the last modified.
391
        tree = self.make_branch_and_tree('.')
392
        self.build_tree(['file'])
393
        self._add_commit_check_unchanged(tree, 'file')
394
395
    def test_last_modified_revision_after_commit_link_unchanged(self):
396
        # committing without changing a link does not change the last modified.
397
        self.requireFeature(tests.SymlinkFeature)
398
        tree = self.make_branch_and_tree('.')
399
        os.symlink('target', 'link')
400
        self._add_commit_check_unchanged(tree, 'link')
401
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
402
    def _add_commit_renamed_check_changed(self, tree, name,
403
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
404
        def rename():
405
            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.
406
        self._add_commit_change_check_changed(tree, name, rename,
407
            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.
408
409
    def test_last_modified_revision_after_rename_dir_changes(self):
410
        # renaming a dir changes the last modified.
411
        tree = self.make_branch_and_tree('.')
412
        self.build_tree(['dir/'])
413
        self._add_commit_renamed_check_changed(tree, 'dir')
414
415
    def test_last_modified_revision_after_rename_file_changes(self):
416
        # renaming a file changes the last modified.
417
        tree = self.make_branch_and_tree('.')
418
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
419
        self._add_commit_renamed_check_changed(tree, 'file',
420
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
421
422
    def test_last_modified_revision_after_rename_link_changes(self):
423
        # renaming a link changes the last modified.
424
        self.requireFeature(tests.SymlinkFeature)
425
        tree = self.make_branch_and_tree('.')
426
        os.symlink('target', 'link')
427
        self._add_commit_renamed_check_changed(tree, 'link')
428
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
429
    def _add_commit_reparent_check_changed(self, tree, name,
430
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
431
        self.build_tree(['newparent/'])
432
        tree.add(['newparent'])
433
        def reparent():
434
            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.
435
        self._add_commit_change_check_changed(tree, name, reparent,
436
            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.
437
438
    def test_last_modified_revision_after_reparent_dir_changes(self):
439
        # reparenting a dir changes the last modified.
440
        tree = self.make_branch_and_tree('.')
441
        self.build_tree(['dir/'])
442
        self._add_commit_reparent_check_changed(tree, 'dir')
443
444
    def test_last_modified_revision_after_reparent_file_changes(self):
445
        # reparenting a file changes the last modified.
446
        tree = self.make_branch_and_tree('.')
447
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
448
        self._add_commit_reparent_check_changed(tree, 'file',
449
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
450
451
    def test_last_modified_revision_after_reparent_link_changes(self):
452
        # reparenting a link changes the last modified.
453
        self.requireFeature(tests.SymlinkFeature)
454
        tree = self.make_branch_and_tree('.')
455
        os.symlink('target', 'link')
456
        self._add_commit_reparent_check_changed(tree, 'link')
457
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
458
    def _add_commit_change_check_changed(self, tree, name, changer,
459
        expect_fs_hash=False):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
460
        tree.add([name], [name + 'id'])
461
        rev1 = tree.commit('')
462
        changer()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
463
        rev2 = self.mini_commit(tree, name, tree.id2path(name + 'id'),
464
            expect_fs_hash=expect_fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
465
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
466
        self.assertEqual(rev1, tree1.inventory[name + 'id'].revision)
467
        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.
468
        file_id = name + 'id'
469
        expected_graph = {}
470
        expected_graph[(file_id, rev1)] = ()
471
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
472
        self.assertFileGraph(expected_graph, tree, (file_id, rev2))
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
473
474
    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.
475
        delta_against_basis=True, expect_fs_hash=False):
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
476
        """Perform a miniature commit looking for record entry results.
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
477
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
478
        :param tree: The tree to commit.
479
        :param name: The path in the basis tree of the tree being committed.
480
        :param new_name: The path in the tree being committed.
481
        :param records_version: True if the commit of new_name is expected to
482
            record a new version.
483
        :param delta_against_basis: True of the commit of new_name is expected
484
            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.
485
        :param expect_fs_hash: True or false to indicate whether we expect a
486
            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
487
        """
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
488
        tree.lock_write()
489
        try:
490
            # mini manual commit here so we can check the return of
491
            # record_entry_contents.
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
492
            parent_ids = tree.get_parent_ids()
493
            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
494
            parent_tree = tree.basis_tree()
495
            parent_tree.lock_read()
496
            self.addCleanup(parent_tree.unlock)
497
            parent_invs = [parent_tree.inventory]
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
498
            for parent_id in parent_ids[1:]:
499
                parent_invs.append(tree.branch.repository.revision_tree(
500
                    parent_id).inventory)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
501
            # root
502
            builder.record_entry_contents(
503
                inventory.make_entry('directory', '', None,
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
504
                    tree.get_root_id()), parent_invs, '', tree,
2776.4.13 by Robert Collins
Merge bzr.dev.
505
                    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
506
            def commit_id(file_id):
507
                old_ie = tree.inventory[file_id]
508
                path = tree.id2path(file_id)
509
                ie = inventory.make_entry(tree.kind(file_id), old_ie.name,
510
                    old_ie.parent_id, file_id)
2776.4.13 by Robert Collins
Merge bzr.dev.
511
                return builder.record_entry_contents(ie, parent_invs, path,
512
                    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
513
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
514
            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
515
            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('')
516
            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
517
                commit_id(parent_id)
518
            # because a change of some sort is meant to have occurred,
519
            # 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.
520
            delta, version_recorded, fs_hash = commit_id(file_id)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
521
            if records_version:
522
                self.assertTrue(version_recorded)
523
            else:
524
                self.assertFalse(version_recorded)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
525
            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.
526
                tree_file_stat = tree.get_file_with_stat(file_id)
527
                tree_file_stat[0].close()
528
                self.assertEqual(2, len(fs_hash))
529
                self.assertEqual(tree.get_file_sha1(file_id), fs_hash[0])
530
                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.
531
            else:
532
                self.assertEqual(None, fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
533
            new_entry = builder.new_inventory[file_id]
534
            if delta_against_basis:
535
                expected_delta = (name, new_name, file_id, new_entry)
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
536
                # The delta should be recorded
537
                self.assertEqual(expected_delta, builder._basis_delta[-1])
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
538
            else:
539
                expected_delta = None
540
            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
541
            builder.finish_inventory()
542
            rev2 = builder.commit('')
543
            tree.set_parent_ids([rev2])
544
        except:
545
            builder.abort()
546
            tree.unlock()
547
            raise
548
        else:
549
            tree.unlock()
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
550
        return rev2
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
551
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.
552
    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.
553
        # all the changes that have occured should be in the ancestry
554
        # (closest to a public per-file graph API we have today)
555
        tree.lock_read()
556
        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.
557
        graph = dict(Graph(tree.branch.repository.texts).iter_ancestry([tip]))
558
        self.assertEqual(expected_graph, graph)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
559
560
    def test_last_modified_revision_after_content_file_changes(self):
561
        # altering a file changes the last modified.
562
        tree = self.make_branch_and_tree('.')
563
        self.build_tree(['file'])
564
        def change_file():
565
            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.
566
        self._add_commit_change_check_changed(tree, 'file', change_file,
567
            expect_fs_hash=True)
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_revision_after_content_link_changes(self):
570
        # changing a link changes the last modified.
571
        self.requireFeature(tests.SymlinkFeature)
572
        tree = self.make_branch_and_tree('.')
573
        os.symlink('target', 'link')
574
        def change_link():
575
            os.unlink('link')
576
            os.symlink('newtarget', 'link')
577
        self._add_commit_change_check_changed(tree, 'link', change_link)
578
579
    def _commit_sprout(self, tree, name):
580
        tree.add([name], [name + 'id'])
581
        rev_id = tree.commit('')
582
        return rev_id, tree.bzrdir.sprout('t2').open_workingtree()
583
584
    def _rename_in_tree(self, tree, name):
585
        tree.rename_one(name, 'new_' + name)
586
        return tree.commit('')
587
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
588
    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.
589
        rev1, tree2 = self._commit_sprout(tree1, name)
590
        # change both sides equally
591
        rev2 = self._rename_in_tree(tree1, name)
592
        rev3 = self._rename_in_tree(tree2, name)
593
        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.
594
        rev4 = self.mini_commit(tree1, 'new_' + name, 'new_' + name,
595
            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.
596
        tree3, = self._get_revtrees(tree1, [rev4])
597
        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.
598
        file_id = name + 'id'
599
        expected_graph = {}
600
        expected_graph[(file_id, rev1)] = ()
601
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
602
        expected_graph[(file_id, rev3)] = ((file_id, rev1),)
603
        expected_graph[(file_id, rev4)] = ((file_id, rev2), (file_id, rev3),)
604
        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.
605
606
    def test_last_modified_revision_after_merge_dir_changes(self):
607
        # merge a dir changes the last modified.
608
        tree1 = self.make_branch_and_tree('t1')
609
        self.build_tree(['t1/dir/'])
610
        self._commit_sprout_rename_merge(tree1, 'dir')
611
612
    def test_last_modified_revision_after_merge_file_changes(self):
613
        # merge a file changes the last modified.
614
        tree1 = self.make_branch_and_tree('t1')
615
        self.build_tree(['t1/file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
616
        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.
617
618
    def test_last_modified_revision_after_merge_link_changes(self):
619
        # merge a link changes the last modified.
620
        self.requireFeature(tests.SymlinkFeature)
621
        tree1 = self.make_branch_and_tree('t1')
622
        os.symlink('target', 't1/link')
623
        self._commit_sprout_rename_merge(tree1, 'link')
624
625
    def _commit_sprout_rename_merge_converged(self, tree1, name):
626
        rev1, tree2 = self._commit_sprout(tree1, name)
627
        # change on the other side to merge back
628
        rev2 = self._rename_in_tree(tree2, name)
629
        tree1.merge_from_branch(tree2.branch)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
630
        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.
631
        tree3, = self._get_revtrees(tree1, [rev2])
632
        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.
633
        file_id = name + 'id'
634
        expected_graph = {}
635
        expected_graph[(file_id, rev1)] = ()
636
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
637
        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.
638
639
    def test_last_modified_revision_after_converged_merge_dir_changes(self):
640
        # merge a dir changes the last modified.
641
        tree1 = self.make_branch_and_tree('t1')
642
        self.build_tree(['t1/dir/'])
643
        self._commit_sprout_rename_merge_converged(tree1, 'dir')
644
645
    def test_last_modified_revision_after_converged_merge_file_changes(self):
646
        # merge a file changes the last modified.
647
        tree1 = self.make_branch_and_tree('t1')
648
        self.build_tree(['t1/file'])
649
        self._commit_sprout_rename_merge_converged(tree1, 'file')
650
651
    def test_last_modified_revision_after_converged_merge_link_changes(self):
652
        # merge a link changes the last modified.
653
        self.requireFeature(tests.SymlinkFeature)
654
        tree1 = self.make_branch_and_tree('t1')
655
        os.symlink('target', 't1/link')
656
        self._commit_sprout_rename_merge_converged(tree1, 'link')
657
658
    def make_dir(self, name):
659
        self.build_tree([name + '/'])
660
661
    def make_file(self, name):
662
        self.build_tree([name])
663
664
    def make_link(self, name):
665
        self.requireFeature(tests.SymlinkFeature)
666
        os.symlink('target', name)
667
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
668
    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.
669
        tree = self.make_branch_and_tree('.')
670
        path = 'name'
671
        make_before(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
672
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
673
        def change_kind():
2831.5.2 by Vincent Ladeuil
Review feedback.
674
            osutils.delete_any(path)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
675
            make_after(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
676
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
677
        self._add_commit_change_check_changed(tree, path, change_kind,
678
            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.
679
680
    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.
681
        self._check_kind_change(self.make_dir, self.make_file,
682
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
683
684
    def test_last_modified_dir_link(self):
685
        self._check_kind_change(self.make_dir, self.make_link)
686
687
    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.
688
        self._check_kind_change(self.make_link, self.make_file,
689
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
690
691
    def test_last_modified_link_dir(self):
692
        self._check_kind_change(self.make_link, self.make_dir)
693
694
    def test_last_modified_file_dir(self):
695
        self._check_kind_change(self.make_file, self.make_dir)
696
697
    def test_last_modified_file_link(self):
698
        self._check_kind_change(self.make_file, self.make_link)
3831.1.1 by John Arbash Meinel
Before allowing commit to succeed, verify the texts will be 'safe'.
699
700
    def test_get_commit_builder_with_invalid_revprops(self):
701
        branch = self.make_branch('.')
702
        branch.repository.lock_write()
703
        self.addCleanup(branch.repository.unlock)
704
        self.assertRaises(ValueError, branch.repository.get_commit_builder,
705
            branch, [], branch.get_config(),
706
            revprops={'invalid': u'property\rwith\r\ninvalid chars'})
707
708
    def test_commit_builder_commit_with_invalid_message(self):
709
        branch = self.make_branch('.')
710
        branch.repository.lock_write()
711
        self.addCleanup(branch.repository.unlock)
712
        builder = branch.repository.get_commit_builder(branch, [],
713
            branch.get_config())
714
        self.addCleanup(branch.repository.abort_write_group)
715
        self.assertRaises(ValueError, builder.commit,
716
            u'Invalid\r\ncommit message\r\n')