/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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1740.3.1 by Jelmer Vernooij
Introduce and use CommitBuilder objects.
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
3775.2.4 by Robert Collins
Start on a CommitBuilder.record_iter_changes method.
61
    def test_finish_inventory_with_record_root(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([])
3775.2.4 by Robert Collins
Start on a CommitBuilder.record_iter_changes method.
66
            repo = tree.branch.repository
2617.6.8 by Robert Collins
Review feedback and documentation.
67
            self.record_root(builder, tree)
68
            builder.finish_inventory()
3775.2.4 by Robert Collins
Start on a CommitBuilder.record_iter_changes method.
69
            repo.commit_write_group()
70
        finally:
71
            tree.unlock()
72
73
    def test_finish_inventory_record_iter_changes(self):
74
        tree = self.make_branch_and_tree(".")
75
        tree.lock_write()
76
        try:
77
            builder = tree.branch.get_commit_builder([])
78
            try:
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
79
                list(builder.record_iter_changes(tree, tree.last_revision(),
80
                    tree.iter_changes(tree.basis_tree())))
3775.2.4 by Robert Collins
Start on a CommitBuilder.record_iter_changes method.
81
                builder.finish_inventory()
82
            except:
83
                builder.abort()
84
                raise
85
            repo = tree.branch.repository
86
            repo.commit_write_group()
2617.6.8 by Robert Collins
Review feedback and documentation.
87
        finally:
88
            tree.unlock()
1740.3.3 by Jelmer Vernooij
Move storing directories and links to commit builder.
89
2749.3.1 by Jelmer Vernooij
Add CommitBuilder.abort().
90
    def test_abort(self):
91
        tree = self.make_branch_and_tree(".")
92
        tree.lock_write()
93
        try:
94
            builder = tree.branch.get_commit_builder([])
95
            self.record_root(builder, tree)
96
            builder.finish_inventory()
97
            builder.abort()
98
        finally:
99
            tree.unlock()
100
3775.2.5 by Robert Collins
CommitBuilder.abort() is callable after record_iter_changes.
101
    def test_abort_record_iter_changes(self):
102
        tree = self.make_branch_and_tree(".")
103
        tree.lock_write()
104
        try:
105
            builder = tree.branch.get_commit_builder([])
106
            try:
3775.2.29 by Robert Collins
Updates to the form of add_inventory_by_delta that landed in trunk.
107
                basis = tree.basis_tree()
108
                last_rev = tree.last_revision()
109
                changes = tree.iter_changes(basis)
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
110
                list(builder.record_iter_changes(tree, last_rev, changes))
3775.2.5 by Robert Collins
CommitBuilder.abort() is callable after record_iter_changes.
111
                builder.finish_inventory()
3775.2.29 by Robert Collins
Updates to the form of add_inventory_by_delta that landed in trunk.
112
            finally:
3775.2.5 by Robert Collins
CommitBuilder.abort() is callable after record_iter_changes.
113
                builder.abort()
114
        finally:
115
            tree.unlock()
116
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
117
    def test_commit_message(self):
1740.3.7 by Jelmer Vernooij
Move committer, log, revprops, timestamp and timezone to CommitBuilder.
118
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
119
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
120
        try:
121
            builder = tree.branch.get_commit_builder([])
122
            self.record_root(builder, tree)
123
            builder.finish_inventory()
124
            rev_id = builder.commit('foo bar blah')
125
        finally:
126
            tree.unlock()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
127
        rev = tree.branch.repository.get_revision(rev_id)
128
        self.assertEqual('foo bar blah', rev.message)
129
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
130
    def test_commit_with_revision_id(self):
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
131
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
132
        tree.lock_write()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
133
        try:
2617.6.8 by Robert Collins
Review feedback and documentation.
134
            # use a unicode revision id to test more corner cases.
135
            # The repository layer is meant to handle this.
136
            revision_id = u'\xc8abc'.encode('utf8')
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.
137
            try:
2617.6.8 by Robert Collins
Review feedback and documentation.
138
                try:
139
                    builder = tree.branch.get_commit_builder([],
140
                        revision_id=revision_id)
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
141
                except errors.NonAsciiRevisionId:
2617.6.8 by Robert Collins
Review feedback and documentation.
142
                    revision_id = 'abc'
143
                    builder = tree.branch.get_commit_builder([],
144
                        revision_id=revision_id)
2831.5.1 by Vincent Ladeuil
Portability fix in TestCommitBuilder for unlink.
145
            except errors.CannotSetRevisionId:
2617.6.8 by Robert Collins
Review feedback and documentation.
146
                # This format doesn't support supplied revision ids
147
                return
2805.6.1 by Robert Collins
Set random_revid on CommitBuilder when a commit generated its own revision id.
148
            self.assertFalse(builder.random_revid)
2617.6.8 by Robert Collins
Review feedback and documentation.
149
            self.record_root(builder, tree)
150
            builder.finish_inventory()
151
            self.assertEqual(revision_id, builder.commit('foo bar'))
152
        finally:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
153
            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.
154
        self.assertTrue(tree.branch.repository.has_revision(revision_id))
155
        # the revision id must be set on the inventory when saving it. This
156
        # does not precisely test that - a repository that wants to can add it
157
        # on deserialisation, but thats all the current contract guarantees
158
        # anyway.
159
        self.assertEqual(revision_id,
160
            tree.branch.repository.get_inventory(revision_id).revision_id)
1740.3.8 by Jelmer Vernooij
Move make_revision() to commit builder.
161
3775.2.6 by Robert Collins
CommitBuilder can specify a revision_id with record_iter_changes.
162
    def test_commit_with_revision_id_record_iter_changes(self):
163
        tree = self.make_branch_and_tree(".")
164
        tree.lock_write()
165
        try:
166
            # use a unicode revision id to test more corner cases.
167
            # The repository layer is meant to handle this.
168
            revision_id = u'\xc8abc'.encode('utf8')
169
            try:
170
                try:
171
                    builder = tree.branch.get_commit_builder([],
172
                        revision_id=revision_id)
173
                except errors.NonAsciiRevisionId:
174
                    revision_id = 'abc'
175
                    builder = tree.branch.get_commit_builder([],
176
                        revision_id=revision_id)
177
            except errors.CannotSetRevisionId:
178
                # This format doesn't support supplied revision ids
179
                return
180
            self.assertFalse(builder.random_revid)
181
            try:
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
182
                list(builder.record_iter_changes(tree, tree.last_revision(),
183
                    tree.iter_changes(tree.basis_tree())))
3775.2.6 by Robert Collins
CommitBuilder can specify a revision_id with record_iter_changes.
184
                builder.finish_inventory()
185
            except:
186
                builder.abort()
187
                raise
188
            self.assertEqual(revision_id, builder.commit('foo bar'))
189
        finally:
190
            tree.unlock()
191
        self.assertTrue(tree.branch.repository.has_revision(revision_id))
192
        # the revision id must be set on the inventory when saving it. This
193
        # does not precisely test that - a repository that wants to can add it
194
        # on deserialisation, but thats all the current contract guarantees
195
        # anyway.
196
        self.assertEqual(revision_id,
197
            tree.branch.repository.get_inventory(revision_id).revision_id)
198
3775.2.7 by Robert Collins
CommitBuilder handles no-change commits to roots properly with record_iter_changes.
199
    def test_commit_without_root_or_record_iter_changes_errors(self):
1910.2.8 by Aaron Bentley
Fix commit_builder when root not passed to record_entry_contents
200
        tree = self.make_branch_and_tree(".")
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
201
        tree.lock_write()
202
        try:
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
203
            self.build_tree(['foo'])
204
            tree.add('foo', 'foo-id')
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
205
            entry = tree.inventory['foo-id']
206
            builder = tree.branch.get_commit_builder([])
2871.1.2 by Robert Collins
* ``CommitBuilder.record_entry_contents`` now requires the root entry of a
207
            self.assertRaises(errors.RootMissing,
2776.4.4 by Robert Collins
Move content summary generation outside of record_entry_contents.
208
                builder.record_entry_contents, entry, [], 'foo', tree,
209
                    tree.path_content_summary('foo'))
2871.1.2 by Robert Collins
* ``CommitBuilder.record_entry_contents`` now requires the root entry of a
210
            builder.abort()
2255.7.8 by John Arbash Meinel
Lock the tree when using a commit builder.
211
        finally:
212
            tree.unlock()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
213
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
214
    def test_commit_unchanged_root(self):
215
        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
216
        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
217
        tree.lock_write()
218
        parent_tree = tree.basis_tree()
219
        parent_tree.lock_read()
220
        self.addCleanup(parent_tree.unlock)
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
221
        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
222
        try:
223
            ie = inventory.make_entry('directory', '', None,
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
224
                    tree.get_root_id())
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
225
            delta, version_recorded, fs_hash = builder.record_entry_contents(
2776.4.13 by Robert Collins
Merge bzr.dev.
226
                ie, [parent_tree.inventory], '', tree,
2871.1.4 by Robert Collins
Merge bzr.dev.
227
                tree.path_content_summary(''))
3775.2.7 by Robert Collins
CommitBuilder handles no-change commits to roots properly with record_iter_changes.
228
            # Regardless of repository root behaviour we should consider this a
229
            # pointless commit.
3775.2.9 by Robert Collins
CommitBuilder handles deletes via record_iter_entries.
230
            self.assertFalse(builder.any_changes())
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
231
            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
232
            # if the repository format recorded a new root revision, that
233
            # should be in the delta
234
            got_new_revision = ie.revision != old_revision_id
235
            if got_new_revision:
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
236
                self.assertEqual(('', '', ie.file_id, ie), delta)
237
                # The delta should be tracked
238
                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
239
            else:
240
                self.assertEqual(None, delta)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
241
            # Directories do not get hashed.
242
            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
243
            builder.abort()
244
        except:
245
            builder.abort()
246
            tree.unlock()
247
            raise
248
        else:
249
            tree.unlock()
1910.2.8 by Aaron Bentley
Fix commit_builder when root not passed to record_entry_contents
250
3775.2.7 by Robert Collins
CommitBuilder handles no-change commits to roots properly with record_iter_changes.
251
    def test_commit_unchanged_root_record_iter_changes(self):
252
        tree = self.make_branch_and_tree(".")
253
        old_revision_id = tree.commit('')
254
        tree.lock_write()
3775.2.9 by Robert Collins
CommitBuilder handles deletes via record_iter_entries.
255
        builder = tree.branch.get_commit_builder([old_revision_id])
3775.2.7 by Robert Collins
CommitBuilder handles no-change commits to roots properly with record_iter_changes.
256
        try:
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
257
            list(builder.record_iter_changes(tree, old_revision_id, []))
3775.2.7 by Robert Collins
CommitBuilder handles no-change commits to roots properly with record_iter_changes.
258
            # Regardless of repository root behaviour we should consider this a
259
            # pointless commit.
3775.2.9 by Robert Collins
CommitBuilder handles deletes via record_iter_entries.
260
            self.assertFalse(builder.any_changes())
3775.2.7 by Robert Collins
CommitBuilder handles no-change commits to roots properly with record_iter_changes.
261
            builder.finish_inventory()
262
            new_root = tree.branch.repository.get_inventory(
263
                builder._new_revision_id).root
264
            if tree.branch.repository.supports_rich_root():
265
                # We should not have seen a new root revision
266
                self.assertEqual(old_revision_id, new_root.revision)
267
            else:
268
                # We should see a new root revision
269
                self.assertNotEqual(old_revision_id, new_root.revision)
270
        finally:
271
            builder.abort()
272
            tree.unlock()
273
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
274
    def test_commit(self):
1740.3.8 by Jelmer Vernooij
Move make_revision() to commit builder.
275
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
276
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
277
        try:
278
            builder = tree.branch.get_commit_builder([])
279
            self.record_root(builder, tree)
280
            builder.finish_inventory()
281
            rev_id = builder.commit('foo bar')
282
        finally:
283
            tree.unlock()
1740.3.9 by Jelmer Vernooij
Make the commit message the first argument of CommitBuilder.commit().
284
        self.assertNotEqual(None, rev_id)
285
        self.assertTrue(tree.branch.repository.has_revision(rev_id))
1757.1.2 by Robert Collins
Bugfix CommitBuilders recording of the inventory revision id.
286
        # the revision id must be set on the inventory when saving it. This does not
287
        # precisely test that - a repository that wants to can add it on deserialisation,
288
        # but thats all the current contract guarantees anyway.
289
        self.assertEqual(rev_id, tree.branch.repository.get_inventory(rev_id).revision_id)
2041.1.1 by John Arbash Meinel
Add a 'get_tree()' call that returns a RevisionTree for the newly committed tree
290
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
291
    def test_get_basis_delta(self):
292
        tree = self.make_branch_and_tree(".")
293
        self.build_tree(["foo"])
294
        tree.add(["foo"], ["foo-id"])
295
        old_revision_id = tree.commit("added foo")
296
        tree.lock_write()
297
        try:
298
            self.build_tree(['bar'])
299
            tree.add(['bar'], ['bar-id'])
300
            basis = tree.branch.repository.revision_tree(old_revision_id)
301
            basis.lock_read()
302
            self.addCleanup(basis.unlock)
303
            builder = tree.branch.get_commit_builder([old_revision_id])
304
            total_delta = []
305
            try:
306
                parent_invs = [basis.inventory]
307
                builder.will_record_deletes()
308
                if builder.record_root_entry:
309
                    ie = basis.inventory.root.copy()
310
                    delta, _, _ = builder.record_entry_contents(ie, parent_invs,
311
                        '', tree, tree.path_content_summary(''))
312
                    if delta is not None:
313
                        total_delta.append(delta)
314
                delta = builder.record_delete("foo", "foo-id")
315
                total_delta.append(delta)
316
                new_bar = inventory.make_entry('file', 'bar',
317
                    parent_id=tree.get_root_id(), file_id='bar-id')
318
                delta, _, _ = builder.record_entry_contents(new_bar, parent_invs,
319
                    'bar', tree, tree.path_content_summary('bar'))
320
                total_delta.append(delta)
321
                # All actions should have been recorded in the basis_delta
322
                self.assertEqual(total_delta, builder.get_basis_delta())
323
                builder.finish_inventory()
324
                builder.commit('delete foo, add bar')
325
            except:
326
                tree.branch.repository.abort_write_group()
327
                raise
328
        finally:
329
            tree.unlock()
330
331
    def test_get_basis_delta_without_notification(self):
332
        tree = self.make_branch_and_tree(".")
333
        old_revision_id = tree.commit('')
334
        tree.lock_write()
335
        try:
336
            parent_tree = tree.basis_tree()
337
            parent_tree.lock_read()
338
            self.addCleanup(parent_tree.unlock)
339
            builder = tree.branch.get_commit_builder([old_revision_id])
340
            # It is an error to expect builder.get_basis_delta() to be correct,
341
            # if you have not also called will_record_deletes() to indicate you
342
            # will be calling record_delete() when appropriate
343
            self.assertRaises(AssertionError, builder.get_basis_delta)
344
            tree.branch.repository.abort_write_group()
345
        finally:
346
            tree.unlock()
347
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
348
    def test_record_delete(self):
349
        tree = self.make_branch_and_tree(".")
350
        self.build_tree(["foo"])
351
        tree.add(["foo"], ["foo-id"])
352
        rev_id = tree.commit("added foo")
353
        # Remove the inventory details for foo-id, because
354
        # record_entry_contents ends up copying root verbatim.
355
        tree.unversion(["foo-id"])
356
        tree.lock_write()
357
        try:
358
            basis = tree.branch.repository.revision_tree(rev_id)
359
            builder = tree.branch.get_commit_builder([rev_id])
360
            try:
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
361
                builder.will_record_deletes()
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
362
                if builder.record_root_entry is True:
363
                    parent_invs = [basis.inventory]
364
                    del basis.inventory.root.children['foo']
365
                    builder.record_entry_contents(basis.inventory.root,
366
                        parent_invs, '', tree, tree.path_content_summary(''))
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
367
                # the delta should be returned, and recorded in _basis_delta
368
                delta = builder.record_delete("foo", "foo-id")
369
                self.assertEqual(("foo", None, "foo-id", None), delta)
370
                self.assertEqual(delta, builder._basis_delta[-1])
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
371
                builder.finish_inventory()
372
                rev_id2 = builder.commit('delete foo')
373
            except:
374
                tree.branch.repository.abort_write_group()
375
                raise
376
        finally:
377
            tree.unlock()
378
        rev_tree = builder.revision_tree()
379
        rev_tree.lock_read()
380
        self.addCleanup(rev_tree.unlock)
381
        self.assertFalse(rev_tree.path2id('foo'))
382
3775.2.9 by Robert Collins
CommitBuilder handles deletes via record_iter_entries.
383
    def test_record_delete_record_iter_changes(self):
384
        tree = self.make_branch_and_tree(".")
385
        self.build_tree(["foo"])
386
        tree.add(["foo"], ["foo-id"])
387
        rev_id = tree.commit("added foo")
388
        tree.lock_write()
389
        try:
390
            builder = tree.branch.get_commit_builder([rev_id])
391
            try:
392
                delete_change = ('foo-id', ('foo', None), True, (True, False),
393
                    (tree.path2id(''), None), ('foo', None), ('file', None),
394
                    (False, None))
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
395
                list(builder.record_iter_changes(tree, rev_id,
396
                    [delete_change]))
3775.2.9 by Robert Collins
CommitBuilder handles deletes via record_iter_entries.
397
                self.assertEqual(("foo", None, "foo-id", None),
3775.2.29 by Robert Collins
Updates to the form of add_inventory_by_delta that landed in trunk.
398
                    builder._basis_delta[0])
3775.2.9 by Robert Collins
CommitBuilder handles deletes via record_iter_entries.
399
                self.assertTrue(builder.any_changes())
400
                builder.finish_inventory()
401
                rev_id2 = builder.commit('delete foo')
402
            except:
403
                builder.abort()
404
                raise
405
        finally:
406
            tree.unlock()
407
        rev_tree = builder.revision_tree()
408
        rev_tree.lock_read()
409
        self.addCleanup(rev_tree.unlock)
410
        self.assertFalse(rev_tree.path2id('foo'))
411
3775.2.2 by Robert Collins
Teach CommitBuilder to accumulate inventory deltas.
412
    def test_record_delete_without_notification(self):
413
        tree = self.make_branch_and_tree(".")
414
        self.build_tree(["foo"])
415
        tree.add(["foo"], ["foo-id"])
416
        rev_id = tree.commit("added foo")
417
        tree.lock_write()
418
        try:
419
            builder = tree.branch.get_commit_builder([rev_id])
420
            try:
421
                self.record_root(builder, tree)
422
                self.assertRaises(AssertionError,
423
                    builder.record_delete, "foo", "foo-id")
424
            finally:
425
                tree.branch.repository.abort_write_group()
426
        finally:
427
            tree.unlock()
428
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
429
    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
430
        tree = self.make_branch_and_tree(".")
2617.6.2 by Robert Collins
Add abort_write_group and wire write_groups into fetch and commit.
431
        tree.lock_write()
2617.6.8 by Robert Collins
Review feedback and documentation.
432
        try:
433
            builder = tree.branch.get_commit_builder([])
434
            self.record_root(builder, tree)
435
            builder.finish_inventory()
436
            rev_id = builder.commit('foo bar')
437
        finally:
438
            tree.unlock()
2041.1.5 by John Arbash Meinel
CommitBuilder.get_tree => CommitBuilder.revision_tree
439
        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
440
        # Just a couple simple tests to ensure that it actually follows
441
        # the RevisionTree api.
442
        self.assertEqual(rev_id, rev_tree.get_revision_id())
443
        self.assertEqual([], rev_tree.get_parent_ids())
2255.7.65 by Robert Collins
Split test_root_revision_entry into tree and repository portions.
444
3775.2.10 by Robert Collins
CommitBuilder gives a revision tree when used with record_iter_contents.
445
    def test_revision_tree_record_iter_changes(self):
446
        tree = self.make_branch_and_tree(".")
447
        tree.lock_write()
448
        try:
449
            builder = tree.branch.get_commit_builder([])
450
            try:
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
451
                list(builder.record_iter_changes(tree,
452
                    _mod_revision.NULL_REVISION,
453
                    tree.iter_changes(tree.basis_tree())))
3775.2.10 by Robert Collins
CommitBuilder gives a revision tree when used with record_iter_contents.
454
                builder.finish_inventory()
455
                rev_id = builder.commit('foo bar')
456
            except:
457
                builder.abort()
458
                raise
459
            rev_tree = builder.revision_tree()
460
            # Just a couple simple tests to ensure that it actually follows
461
            # the RevisionTree api.
462
            self.assertEqual(rev_id, rev_tree.get_revision_id())
463
            self.assertEqual([], rev_tree.get_parent_ids())
464
        finally:
465
            tree.unlock()
466
2255.7.65 by Robert Collins
Split test_root_revision_entry into tree and repository portions.
467
    def test_root_entry_has_revision(self):
468
        # test the root revision created and put in the basis
469
        # has the right rev id.
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
470
        # XXX: RBC 20081118 - this test is too big, it depends on the exact
471
        # behaviour of tree methods and so on; it should be written to the
472
        # commit builder interface directly.
2255.7.65 by Robert Collins
Split test_root_revision_entry into tree and repository portions.
473
        tree = self.make_branch_and_tree('.')
474
        rev_id = tree.commit('message')
475
        basis_tree = tree.basis_tree()
476
        basis_tree.lock_read()
477
        self.addCleanup(basis_tree.unlock)
478
        self.assertEqual(rev_id, basis_tree.inventory.root.revision)
479
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
480
    def _get_revtrees(self, tree, revision_ids):
2592.3.214 by Robert Collins
Merge bzr.dev.
481
        tree.lock_read()
482
        try:
483
            trees = list(tree.branch.repository.revision_trees(revision_ids))
484
            for _tree in trees:
485
                _tree.lock_read()
486
                self.addCleanup(_tree.unlock)
487
            return trees
488
        finally:
489
            tree.unlock()
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
490
491
    def test_last_modified_revision_after_commit_root_unchanged(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
492
        # 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.
493
        # last modified except on non-rich-root-repositories.
494
        tree = self.make_branch_and_tree('.')
495
        rev1 = tree.commit('')
496
        rev2 = tree.commit('')
497
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
498
        self.assertEqual(rev1, tree1.inventory.root.revision)
499
        if tree.branch.repository.supports_rich_root():
500
            self.assertEqual(rev1, tree2.inventory.root.revision)
501
        else:
502
            self.assertEqual(rev2, tree2.inventory.root.revision)
503
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
504
    def _add_commit_check_unchanged(self, tree, name, mini_commit=None):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
505
        tree.add([name], [name + 'id'])
4183.5.3 by Robert Collins
Fix typo.
506
        self._commit_check_unchanged(tree, name, name + 'id',
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
507
            mini_commit=mini_commit)
508
509
    def _commit_check_unchanged(self, tree, name, file_id, mini_commit=None):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
510
        rev1 = tree.commit('')
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
511
        if mini_commit is None:
512
            mini_commit = self.mini_commit
513
        rev2 = 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.
514
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
515
        self.assertEqual(rev1, tree1.inventory[file_id].revision)
516
        self.assertEqual(rev1, tree2.inventory[file_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.
517
        expected_graph = {}
518
        expected_graph[(file_id, rev1)] = ()
519
        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.
520
521
    def test_last_modified_revision_after_commit_dir_unchanged(self):
522
        # committing without changing a dir does not change the last modified.
523
        tree = self.make_branch_and_tree('.')
524
        self.build_tree(['dir/'])
525
        self._add_commit_check_unchanged(tree, 'dir')
526
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
527
    def test_last_modified_revision_after_commit_dir_unchanged_ric(self):
528
        # committing without changing a dir does not change the last modified.
529
        tree = self.make_branch_and_tree('.')
530
        self.build_tree(['dir/'])
531
        self._add_commit_check_unchanged(tree, 'dir',
532
            mini_commit=self.mini_commit_record_iter_changes)
533
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
534
    def test_last_modified_revision_after_commit_dir_contents_unchanged(self):
535
        # committing without changing a dir does not change the last modified
536
        # of the dir even the dirs contents are changed.
537
        tree = self.make_branch_and_tree('.')
538
        self.build_tree(['dir/'])
539
        tree.add(['dir'], ['dirid'])
540
        rev1 = tree.commit('')
541
        self.build_tree(['dir/content'])
542
        tree.add(['dir/content'], ['contentid'])
543
        rev2 = tree.commit('')
544
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
545
        self.assertEqual(rev1, tree1.inventory['dirid'].revision)
546
        self.assertEqual(rev1, tree2.inventory['dirid'].revision)
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.
547
        file_id = 'dirid'
548
        expected_graph = {}
549
        expected_graph[(file_id, rev1)] = ()
550
        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.
551
552
    def test_last_modified_revision_after_commit_file_unchanged(self):
553
        # committing without changing a file does not change the last modified.
554
        tree = self.make_branch_and_tree('.')
555
        self.build_tree(['file'])
556
        self._add_commit_check_unchanged(tree, 'file')
557
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
558
    def test_last_modified_revision_after_commit_file_unchanged_ric(self):
559
        # committing without changing a file does not change the last modified.
560
        tree = self.make_branch_and_tree('.')
561
        self.build_tree(['file'])
562
        self._add_commit_check_unchanged(tree, 'file',
563
            mini_commit=self.mini_commit_record_iter_changes)
564
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
565
    def test_last_modified_revision_after_commit_link_unchanged(self):
566
        # committing without changing a link does not change the last modified.
567
        self.requireFeature(tests.SymlinkFeature)
568
        tree = self.make_branch_and_tree('.')
569
        os.symlink('target', 'link')
570
        self._add_commit_check_unchanged(tree, 'link')
571
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
572
    def test_last_modified_revision_after_commit_link_unchanged_ric(self):
4183.5.6 by Robert Collins
Review caught a bogus change to test_last_modified_revision_after_commit_link_unchanged_ric.
573
        # committing without changing a link does not change the last modified.
574
        self.requireFeature(tests.SymlinkFeature)
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
575
        tree = self.make_branch_and_tree('.')
576
        os.symlink('target', 'link')
577
        self._add_commit_check_unchanged(tree, 'link',
578
            mini_commit=self.mini_commit_record_iter_changes)
579
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
580
    def test_last_modified_revision_after_commit_reference_unchanged(self):
581
        # committing without changing a subtree does not change the last
582
        # modified.
583
        tree = self.make_branch_and_tree('.')
584
        subtree = self.make_reference('reference')
585
        try:
586
            tree.add_reference(subtree)
587
            self._commit_check_unchanged(tree, 'reference',
588
                subtree.get_root_id())
589
        except errors.UnsupportedOperation:
590
            return
591
592
    def test_last_modified_revision_after_commit_reference_unchanged_ric(self):
593
        # committing without changing a subtree does not change the last
594
        # modified.
595
        tree = self.make_branch_and_tree('.')
596
        subtree = self.make_reference('reference')
597
        try:
598
            tree.add_reference(subtree)
599
            self._commit_check_unchanged(tree, 'reference',
600
                subtree.get_root_id(),
601
                mini_commit=self.mini_commit_record_iter_changes)
602
        except errors.UnsupportedOperation:
603
            return
604
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
605
    def _add_commit_renamed_check_changed(self, tree, name,
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
606
        expect_fs_hash=False, mini_commit=None):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
607
        def rename():
608
            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.
609
        self._add_commit_change_check_changed(tree, name, rename,
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
610
            expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
611
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
612
    def _commit_renamed_check_changed(self, tree, name, file_id,
613
        expect_fs_hash=False, mini_commit=None):
614
        def rename():
615
            tree.rename_one(name, 'new_' + name)
616
        self._commit_change_check_changed(tree, name, file_id, rename,
617
            expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
618
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
619
    def test_last_modified_revision_after_rename_dir_changes(self):
620
        # renaming a dir changes the last modified.
621
        tree = self.make_branch_and_tree('.')
622
        self.build_tree(['dir/'])
623
        self._add_commit_renamed_check_changed(tree, 'dir')
624
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
625
    def test_last_modified_revision_after_rename_dir_changes_ric(self):
626
        # renaming a dir changes the last modified.
627
        tree = self.make_branch_and_tree('.')
628
        self.build_tree(['dir/'])
629
        self._add_commit_renamed_check_changed(tree, 'dir',
630
            mini_commit=self.mini_commit_record_iter_changes)
631
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
632
    def test_last_modified_revision_after_rename_file_changes(self):
633
        # renaming a file changes the last modified.
634
        tree = self.make_branch_and_tree('.')
635
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
636
        self._add_commit_renamed_check_changed(tree, 'file',
637
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
638
3775.2.12 by Robert Collins
CommitBuilder.record_iter_changes handles renamed files.
639
    def test_last_modified_revision_after_rename_file_changes_ric(self):
640
        # renaming a file changes the last modified.
641
        tree = self.make_branch_and_tree('.')
642
        self.build_tree(['file'])
643
        self._add_commit_renamed_check_changed(tree, 'file',
644
            expect_fs_hash=True,
645
            mini_commit=self.mini_commit_record_iter_changes)
646
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
647
    def test_last_modified_revision_after_rename_link_changes(self):
648
        # renaming a link changes the last modified.
649
        self.requireFeature(tests.SymlinkFeature)
650
        tree = self.make_branch_and_tree('.')
651
        os.symlink('target', 'link')
652
        self._add_commit_renamed_check_changed(tree, 'link')
653
3775.2.13 by Robert Collins
CommitBuilder.record_iter_changes handles renamed symlinks.
654
    def test_last_modified_revision_after_rename_link_changes_ric(self):
655
        # renaming a link changes the last modified.
656
        self.requireFeature(tests.SymlinkFeature)
657
        tree = self.make_branch_and_tree('.')
658
        os.symlink('target', 'link')
659
        self._add_commit_renamed_check_changed(tree, 'link',
660
            mini_commit=self.mini_commit_record_iter_changes)
661
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
662
    def test_last_modified_revision_after_rename_ref_changes(self):
663
        # renaming a reference changes the last modified.
664
        tree = self.make_branch_and_tree('.')
665
        subtree = self.make_reference('reference')
666
        try:
667
            tree.add_reference(subtree)
668
            self._commit_renamed_check_changed(tree, 'reference',
669
                subtree.get_root_id())
670
        except errors.UnsupportedOperation:
671
            return
672
673
    def test_last_modified_revision_after_rename_ref_changes_ric(self):
674
        # renaming a reference changes the last modified.
675
        tree = self.make_branch_and_tree('.')
676
        subtree = self.make_reference('reference')
677
        try:
678
            tree.add_reference(subtree)
679
            self._commit_renamed_check_changed(tree, 'reference',
680
                subtree.get_root_id(),
681
                mini_commit=self.mini_commit_record_iter_changes)
682
        except errors.UnsupportedOperation:
683
            return
684
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
685
    def _add_commit_reparent_check_changed(self, tree, name,
3775.2.14 by Robert Collins
CommitBuilder.record_iter_changes handles reparented directories.
686
        expect_fs_hash=False, mini_commit=None):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
687
        self.build_tree(['newparent/'])
688
        tree.add(['newparent'])
689
        def reparent():
690
            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.
691
        self._add_commit_change_check_changed(tree, name, reparent,
3775.2.14 by Robert Collins
CommitBuilder.record_iter_changes handles reparented directories.
692
            expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
693
694
    def test_last_modified_revision_after_reparent_dir_changes(self):
695
        # reparenting a dir changes the last modified.
696
        tree = self.make_branch_and_tree('.')
697
        self.build_tree(['dir/'])
698
        self._add_commit_reparent_check_changed(tree, 'dir')
699
3775.2.14 by Robert Collins
CommitBuilder.record_iter_changes handles reparented directories.
700
    def test_last_modified_revision_after_reparent_dir_changes_ric(self):
701
        # reparenting a dir changes the last modified.
702
        tree = self.make_branch_and_tree('.')
703
        self.build_tree(['dir/'])
704
        self._add_commit_reparent_check_changed(tree, 'dir',
705
            mini_commit=self.mini_commit_record_iter_changes)
706
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
707
    def test_last_modified_revision_after_reparent_file_changes(self):
708
        # reparenting a file changes the last modified.
709
        tree = self.make_branch_and_tree('.')
710
        self.build_tree(['file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
711
        self._add_commit_reparent_check_changed(tree, 'file',
712
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
713
3775.2.15 by Robert Collins
CommitBuilder.record_iter_changes handles reparented files.
714
    def test_last_modified_revision_after_reparent_file_changes_ric(self):
715
        # reparenting a file changes the last modified.
716
        tree = self.make_branch_and_tree('.')
717
        self.build_tree(['file'])
718
        self._add_commit_reparent_check_changed(tree, 'file',
3775.2.17 by Robert Collins
CommitBuilder.record_iter_changes handles changed files.
719
            expect_fs_hash=True,
720
            mini_commit=self.mini_commit_record_iter_changes)
3775.2.15 by Robert Collins
CommitBuilder.record_iter_changes handles reparented files.
721
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
722
    def test_last_modified_revision_after_reparent_link_changes(self):
723
        # reparenting a link changes the last modified.
724
        self.requireFeature(tests.SymlinkFeature)
725
        tree = self.make_branch_and_tree('.')
726
        os.symlink('target', 'link')
727
        self._add_commit_reparent_check_changed(tree, 'link')
728
3775.2.16 by Robert Collins
CommitBuilder.record_iter_changes handles reparented symlinks.
729
    def test_last_modified_revision_after_reparent_link_changes_ric(self):
730
        # reparenting a link changes the last modified.
731
        self.requireFeature(tests.SymlinkFeature)
732
        tree = self.make_branch_and_tree('.')
733
        os.symlink('target', 'link')
734
        self._add_commit_reparent_check_changed(tree, 'link',
735
            mini_commit=self.mini_commit_record_iter_changes)
736
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
737
    def _add_commit_change_check_changed(self, tree, name, changer,
4241.14.12 by Vincent Ladeuil
Far too many modifications for a single commit, need to restart.
738
        expect_fs_hash=False, mini_commit=None, file_id=None):
739
        if file_id is None:
740
            file_id = name + 'id'
741
        tree.add([name], [file_id])
742
        self._commit_change_check_changed(
743
            tree, name, file_id,
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
744
            changer, expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
745
4183.5.3 by Robert Collins
Fix typo.
746
    def _commit_change_check_changed(self, tree, name, file_id, changer,
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
747
        expect_fs_hash=False, mini_commit=None):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
748
        rev1 = tree.commit('')
749
        changer()
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
750
        if mini_commit is None:
751
            mini_commit = self.mini_commit
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
752
        rev2 = mini_commit(tree, name, tree.id2path(file_id),
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
753
            expect_fs_hash=expect_fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
754
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
755
        self.assertEqual(rev1, tree1.inventory[file_id].revision)
756
        self.assertEqual(rev2, tree2.inventory[file_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.
757
        expected_graph = {}
758
        expected_graph[(file_id, rev1)] = ()
759
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
760
        self.assertFileGraph(expected_graph, tree, (file_id, rev2))
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
761
762
    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.
763
        delta_against_basis=True, expect_fs_hash=False):
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
764
        """Perform a miniature commit looking for record entry results.
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
765
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
766
        :param tree: The tree to commit.
767
        :param name: The path in the basis tree of the tree being committed.
768
        :param new_name: The path in the tree being committed.
769
        :param records_version: True if the commit of new_name is expected to
770
            record a new version.
771
        :param delta_against_basis: True of the commit of new_name is expected
772
            to have a delta against the basis.
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
773
        :param expect_fs_hash: True or false to indicate whether we expect a
774
            file hash to be returned from the record_entry_contents call.
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
775
        """
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
776
        tree.lock_write()
777
        try:
778
            # mini manual commit here so we can check the return of
779
            # record_entry_contents.
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
780
            parent_ids = tree.get_parent_ids()
781
            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
782
            parent_tree = tree.basis_tree()
783
            parent_tree.lock_read()
784
            self.addCleanup(parent_tree.unlock)
785
            parent_invs = [parent_tree.inventory]
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
786
            for parent_id in parent_ids[1:]:
787
                parent_invs.append(tree.branch.repository.revision_tree(
788
                    parent_id).inventory)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
789
            # root
790
            builder.record_entry_contents(
791
                inventory.make_entry('directory', '', None,
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
792
                    tree.get_root_id()), parent_invs, '', tree,
2776.4.13 by Robert Collins
Merge bzr.dev.
793
                    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
794
            def commit_id(file_id):
795
                old_ie = tree.inventory[file_id]
796
                path = tree.id2path(file_id)
797
                ie = inventory.make_entry(tree.kind(file_id), old_ie.name,
798
                    old_ie.parent_id, file_id)
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
799
                content_summary = tree.path_content_summary(path)
800
                if content_summary[0] == 'tree-reference':
801
                    content_summary = content_summary[:3] + (
802
                        tree.get_reference_revision(file_id),)
2776.4.13 by Robert Collins
Merge bzr.dev.
803
                return builder.record_entry_contents(ie, parent_invs, path,
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
804
                    tree, content_summary)
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
805
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
806
            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
807
            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('')
808
            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
809
                commit_id(parent_id)
810
            # because a change of some sort is meant to have occurred,
811
            # 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.
812
            delta, version_recorded, fs_hash = commit_id(file_id)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
813
            if records_version:
814
                self.assertTrue(version_recorded)
815
            else:
816
                self.assertFalse(version_recorded)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
817
            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.
818
                tree_file_stat = tree.get_file_with_stat(file_id)
819
                tree_file_stat[0].close()
820
                self.assertEqual(2, len(fs_hash))
821
                self.assertEqual(tree.get_file_sha1(file_id), fs_hash[0])
822
                self.assertEqualStat(tree_file_stat[1], fs_hash[1])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
823
            else:
824
                self.assertEqual(None, fs_hash)
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
825
            new_entry = builder.new_inventory[file_id]
826
            if delta_against_basis:
827
                expected_delta = (name, new_name, file_id, new_entry)
3879.2.5 by John Arbash Meinel
Change record_delete() to return the delta.
828
                # The delta should be recorded
829
                self.assertEqual(expected_delta, builder._basis_delta[-1])
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
830
            else:
831
                expected_delta = None
832
            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
833
            builder.finish_inventory()
834
            rev2 = builder.commit('')
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
835
        except:
836
            builder.abort()
837
            tree.unlock()
838
            raise
4245.1.1 by Ian Clatworthy
minor test clean-ups & _reconcile_pack API
839
        try:
840
            tree.set_parent_ids([rev2])
841
        finally:
2825.5.1 by Robert Collins
* Committing a change which is not a merge and does not change the number of
842
            tree.unlock()
2871.1.3 by Robert Collins
* The CommitBuilder method ``record_entry_contents`` now returns summary
843
        return rev2
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
844
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
845
    def mini_commit_record_iter_changes(self, tree, name, new_name,
846
        records_version=True, delta_against_basis=True, expect_fs_hash=False):
847
        """Perform a miniature commit looking for record entry results.
848
849
        This version uses the record_iter_changes interface.
850
        
851
        :param tree: The tree to commit.
852
        :param name: The path in the basis tree of the tree being committed.
853
        :param new_name: The path in the tree being committed.
854
        :param records_version: True if the commit of new_name is expected to
855
            record a new version.
856
        :param delta_against_basis: True of the commit of new_name is expected
857
            to have a delta against the basis.
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
858
        :param expect_fs_hash: If true, looks for a fs hash output from
859
            record_iter_changes.
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
860
        """
861
        tree.lock_write()
862
        try:
863
            # mini manual commit here so we can check the return of
864
            # record_entry_contents.
865
            parent_ids = tree.get_parent_ids()
866
            builder = tree.branch.get_commit_builder(parent_ids)
867
            parent_tree = tree.basis_tree()
868
            parent_tree.lock_read()
869
            self.addCleanup(parent_tree.unlock)
870
            parent_invs = [parent_tree.inventory]
871
            for parent_id in parent_ids[1:]:
872
                parent_invs.append(tree.branch.repository.revision_tree(
873
                    parent_id).inventory)
874
            changes = list(tree.iter_changes(parent_tree))
4183.5.4 by Robert Collins
Turn record_iter_changes into a generator to emit file system hashes.
875
            result = list(builder.record_iter_changes(tree, parent_ids[0],
876
                changes))
877
            file_id = tree.path2id(new_name)
878
            if expect_fs_hash:
879
                tree_file_stat = tree.get_file_with_stat(file_id)
880
                tree_file_stat[0].close()
881
                self.assertLength(1, result)
882
                result = result[0]
883
                self.assertEqual(result[:2], (file_id, new_name))
884
                self.assertEqual(result[2][0], tree.get_file_sha1(file_id))
885
                self.assertEqualStat(result[2][1], tree_file_stat[1])
886
            else:
887
                self.assertEqual([], result)
3775.2.29 by Robert Collins
Updates to the form of add_inventory_by_delta that landed in trunk.
888
            delta = builder._basis_delta
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
889
            delta_dict = dict((change[2], change) for change in delta)
3775.2.22 by Robert Collins
CommitBuilder.record_iter_changes handles changed-in-branch directories.
890
            version_recorded = (file_id in delta_dict and
891
                delta_dict[file_id][3] is not None and
892
                delta_dict[file_id][3].revision == builder._new_revision_id)
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
893
            if records_version:
894
                self.assertTrue(version_recorded)
895
            else:
896
                self.assertFalse(version_recorded)
4789.27.2 by John Arbash Meinel
Add some tests that the record-iter-changes is setting inv_sha1 correctly.
897
            self.assertIs(None, builder.new_inventory)
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
898
            builder.finish_inventory()
4789.27.2 by John Arbash Meinel
Add some tests that the record-iter-changes is setting inv_sha1 correctly.
899
            inv_key = (builder._new_revision_id,)
900
            inv_sha1 = tree.branch.repository.inventories.get_sha1s(
901
                            [inv_key])[inv_key]
902
            self.assertEqual(inv_sha1, builder.inv_sha1)
4789.27.4 by John Arbash Meinel
Robert says that self.new_inventory shouldn't be set.
903
            self.assertIs(None, builder.new_inventory)
3775.2.11 by Robert Collins
CommitBuilder handles renamed directory and unmodified entries with single parents, for record_iter_changes.
904
            new_inventory = builder.revision_tree().inventory
905
            new_entry = new_inventory[file_id]
906
            if delta_against_basis:
907
                expected_delta = (name, new_name, file_id, new_entry)
908
                self.assertEqual(expected_delta, delta_dict[file_id])
909
            else:
910
                expected_delta = None
911
                self.assertFalse(version_recorded)
912
            rev2 = builder.commit('')
913
            tree.set_parent_ids([rev2])
914
        except:
915
            builder.abort()
916
            tree.unlock()
917
            raise
918
        else:
919
            tree.unlock()
920
        return rev2
921
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.
922
    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.
923
        # all the changes that have occured should be in the ancestry
924
        # (closest to a public per-file graph API we have today)
925
        tree.lock_read()
926
        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.
927
        graph = dict(Graph(tree.branch.repository.texts).iter_ancestry([tip]))
928
        self.assertEqual(expected_graph, graph)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
929
930
    def test_last_modified_revision_after_content_file_changes(self):
931
        # altering a file changes the last modified.
932
        tree = self.make_branch_and_tree('.')
933
        self.build_tree(['file'])
934
        def change_file():
935
            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.
936
        self._add_commit_change_check_changed(tree, 'file', change_file,
937
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
938
3775.2.17 by Robert Collins
CommitBuilder.record_iter_changes handles changed files.
939
    def test_last_modified_revision_after_content_file_changes_ric(self):
940
        # altering a file changes the last modified.
941
        tree = self.make_branch_and_tree('.')
942
        self.build_tree(['file'])
943
        def change_file():
944
            tree.put_file_bytes_non_atomic('fileid', 'new content')
945
        self._add_commit_change_check_changed(tree, 'file', change_file,
946
            expect_fs_hash=True,
947
            mini_commit=self.mini_commit_record_iter_changes)
948
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
949
    def test_last_modified_revision_after_content_link_changes(self):
950
        # changing a link changes the last modified.
951
        self.requireFeature(tests.SymlinkFeature)
952
        tree = self.make_branch_and_tree('.')
953
        os.symlink('target', 'link')
954
        def change_link():
955
            os.unlink('link')
956
            os.symlink('newtarget', 'link')
957
        self._add_commit_change_check_changed(tree, 'link', change_link)
958
4241.14.12 by Vincent Ladeuil
Far too many modifications for a single commit, need to restart.
959
    def _test_last_mod_rev_after_content_link_changes_ric(
960
        self, link, target, newtarget, file_id=None):
961
        if file_id is None:
962
            file_id = link
3775.2.18 by Robert Collins
CommitBuilder.record_iter_changes handles changed symlinks.
963
        # changing a link changes the last modified.
964
        self.requireFeature(tests.SymlinkFeature)
965
        tree = self.make_branch_and_tree('.')
4241.14.12 by Vincent Ladeuil
Far too many modifications for a single commit, need to restart.
966
        os.symlink(target, link)
3775.2.18 by Robert Collins
CommitBuilder.record_iter_changes handles changed symlinks.
967
        def change_link():
4241.14.12 by Vincent Ladeuil
Far too many modifications for a single commit, need to restart.
968
            os.unlink(link)
969
            os.symlink(newtarget, link)
970
        self._add_commit_change_check_changed(
971
            tree, link, change_link,
972
            mini_commit=self.mini_commit_record_iter_changes,
973
            file_id=file_id)
974
975
    def test_last_modified_rev_after_content_link_changes_ric(self):
976
        self._test_last_mod_rev_after_content_link_changes_ric(
977
            'link', 'target', 'newtarget')
978
979
    def test_last_modified_rev_after_content_unicode_link_changes_ric(self):
980
        self.requireFeature(tests.UnicodeFilenameFeature)
981
        self._test_last_mod_rev_after_content_link_changes_ric(
982
            u'li\u1234nk', u'targ\N{Euro Sign}t', u'n\N{Euro Sign}wtarget',
983
984
            file_id=u'li\u1234nk'.encode('UTF-8'))
3775.2.18 by Robert Collins
CommitBuilder.record_iter_changes handles changed symlinks.
985
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
986
    def _commit_sprout(self, tree, name):
987
        tree.add([name], [name + 'id'])
988
        rev_id = tree.commit('')
989
        return rev_id, tree.bzrdir.sprout('t2').open_workingtree()
990
991
    def _rename_in_tree(self, tree, name):
992
        tree.rename_one(name, 'new_' + name)
993
        return tree.commit('')
994
3775.2.19 by Robert Collins
CommitBuilder.record_iter_changes handles merged directories.
995
    def _commit_sprout_rename_merge(self, tree1, name, expect_fs_hash=False,
996
        mini_commit=None):
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
997
        """Do a rename in both trees."""
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
998
        rev1, tree2 = self._commit_sprout(tree1, name)
999
        # change both sides equally
1000
        rev2 = self._rename_in_tree(tree1, name)
1001
        rev3 = self._rename_in_tree(tree2, name)
1002
        tree1.merge_from_branch(tree2.branch)
3775.2.19 by Robert Collins
CommitBuilder.record_iter_changes handles merged directories.
1003
        if mini_commit is None:
1004
            mini_commit = self.mini_commit
1005
        rev4 = mini_commit(tree1, 'new_' + name, 'new_' + name,
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
1006
            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.
1007
        tree3, = self._get_revtrees(tree1, [rev4])
1008
        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.
1009
        file_id = name + 'id'
1010
        expected_graph = {}
1011
        expected_graph[(file_id, rev1)] = ()
1012
        expected_graph[(file_id, rev2)] = ((file_id, rev1),)
1013
        expected_graph[(file_id, rev3)] = ((file_id, rev1),)
1014
        expected_graph[(file_id, rev4)] = ((file_id, rev2), (file_id, rev3),)
1015
        self.assertFileGraph(expected_graph, tree1, (file_id, rev4))
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1016
1017
    def test_last_modified_revision_after_merge_dir_changes(self):
1018
        # merge a dir changes the last modified.
1019
        tree1 = self.make_branch_and_tree('t1')
1020
        self.build_tree(['t1/dir/'])
1021
        self._commit_sprout_rename_merge(tree1, 'dir')
1022
3775.2.19 by Robert Collins
CommitBuilder.record_iter_changes handles merged directories.
1023
    def test_last_modified_revision_after_merge_dir_changes_ric(self):
1024
        # merge a dir changes the last modified.
1025
        tree1 = self.make_branch_and_tree('t1')
1026
        self.build_tree(['t1/dir/'])
1027
        self._commit_sprout_rename_merge(tree1, 'dir',
1028
            mini_commit=self.mini_commit_record_iter_changes)
1029
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1030
    def test_last_modified_revision_after_merge_file_changes(self):
1031
        # merge a file changes the last modified.
1032
        tree1 = self.make_branch_and_tree('t1')
1033
        self.build_tree(['t1/file'])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
1034
        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.
1035
3775.2.20 by Robert Collins
CommitBuilder.record_iter_changes handles merged files.
1036
    def test_last_modified_revision_after_merge_file_changes_ric(self):
1037
        # merge a file changes the last modified.
1038
        tree1 = self.make_branch_and_tree('t1')
1039
        self.build_tree(['t1/file'])
1040
        self._commit_sprout_rename_merge(tree1, 'file', expect_fs_hash=True,
1041
            mini_commit=self.mini_commit_record_iter_changes)
1042
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1043
    def test_last_modified_revision_after_merge_link_changes(self):
1044
        # merge a link changes the last modified.
1045
        self.requireFeature(tests.SymlinkFeature)
1046
        tree1 = self.make_branch_and_tree('t1')
1047
        os.symlink('target', 't1/link')
1048
        self._commit_sprout_rename_merge(tree1, 'link')
1049
3775.2.21 by Robert Collins
CommitBuilder.record_iter_changes handles merged symlinks.
1050
    def test_last_modified_revision_after_merge_link_changes_ric(self):
1051
        # merge a link changes the last modified.
1052
        self.requireFeature(tests.SymlinkFeature)
1053
        tree1 = self.make_branch_and_tree('t1')
1054
        os.symlink('target', 't1/link')
1055
        self._commit_sprout_rename_merge(tree1, 'link',
1056
            mini_commit=self.mini_commit_record_iter_changes)
1057
3775.2.22 by Robert Collins
CommitBuilder.record_iter_changes handles changed-in-branch directories.
1058
    def _commit_sprout_rename_merge_converged(self, tree1, name,
1059
        mini_commit=None):
1060
        # Make a merge which just incorporates a change from a branch:
1061
        # The per-file graph is straight line, and no alteration occurs
1062
        # in the inventory.
4183.5.9 by Robert Collins
Fix creating new revisions of files when merging.
1063
        # Part 1: change in the merged branch.
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1064
        rev1, tree2 = self._commit_sprout(tree1, name)
1065
        # change on the other side to merge back
1066
        rev2 = self._rename_in_tree(tree2, name)
1067
        tree1.merge_from_branch(tree2.branch)
3775.2.22 by Robert Collins
CommitBuilder.record_iter_changes handles changed-in-branch directories.
1068
        if mini_commit is None:
1069
            mini_commit = self.mini_commit
4183.5.9 by Robert Collins
Fix creating new revisions of files when merging.
1070
        def _check_graph(in_tree, changed_in_tree):
1071
            rev3 = mini_commit(in_tree, name, 'new_' + name, False,
1072
                delta_against_basis=changed_in_tree)
1073
            tree3, = self._get_revtrees(in_tree, [rev2])
1074
            self.assertEqual(rev2, tree3.inventory[name + 'id'].revision)
1075
            file_id = name + 'id'
1076
            expected_graph = {}
1077
            expected_graph[(file_id, rev1)] = ()
1078
            expected_graph[(file_id, rev2)] = ((file_id, rev1),)
1079
            self.assertFileGraph(expected_graph, in_tree, (file_id, rev2))
1080
        _check_graph(tree1, True)
1081
        # Part 2: change in the merged into branch - we use tree2 that has a
1082
        # change to name, branch tree1 and give it an unrelated change, then
1083
        # merge that to t2.
1084
        other_tree = tree1.bzrdir.sprout('t3').open_workingtree()
1085
        other_rev = other_tree.commit('')
1086
        tree2.merge_from_branch(other_tree.branch)
1087
        _check_graph(tree2, False)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1088
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
1089
    def _commit_sprout_make_merge(self, tree1, make, mini_commit=None):
1090
        # Make a merge which incorporates the addition of a new object to
1091
        # another branch. The per-file graph shows no additional change
1092
        # in the merge because its a straight line.
1093
        rev1 = tree1.commit('')
1094
        tree2 = tree1.bzrdir.sprout('t2').open_workingtree()
1095
        # make and commit on the other side to merge back
1096
        make('t2/name')
1097
        file_id = 'nameid'
1098
        tree2.add(['name'], [file_id])
1099
        rev2 = tree2.commit('')
1100
        tree1.merge_from_branch(tree2.branch)
1101
        if mini_commit is None:
1102
            mini_commit = self.mini_commit
1103
        rev3 = mini_commit(tree1, None, 'name', False)
1104
        tree3, = self._get_revtrees(tree1, [rev2])
1105
        # in rev2, name should be only changed in rev2
1106
        self.assertEqual(rev2, tree3.inventory[file_id].revision)
1107
        expected_graph = {}
1108
        expected_graph[(file_id, rev2)] = ()
1109
        self.assertFileGraph(expected_graph, tree1, (file_id, rev2))
1110
1111
    def test_last_modified_revision_after_converged_merge_dir_unchanged(self):
1112
        # merge a dir that changed preserves the last modified.
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1113
        tree1 = self.make_branch_and_tree('t1')
1114
        self.build_tree(['t1/dir/'])
1115
        self._commit_sprout_rename_merge_converged(tree1, 'dir')
1116
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
1117
    def test_last_modified_revision_after_converged_merge_dir_unchanged_ric(self):
1118
        # merge a dir that changed preserves the last modified.
3775.2.22 by Robert Collins
CommitBuilder.record_iter_changes handles changed-in-branch directories.
1119
        tree1 = self.make_branch_and_tree('t1')
1120
        self.build_tree(['t1/dir/'])
1121
        self._commit_sprout_rename_merge_converged(tree1, 'dir',
1122
            mini_commit=self.mini_commit_record_iter_changes)
1123
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
1124
    def test_last_modified_revision_after_converged_merge_file_unchanged(self):
1125
        # merge a file that changed preserves the last modified.
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1126
        tree1 = self.make_branch_and_tree('t1')
1127
        self.build_tree(['t1/file'])
1128
        self._commit_sprout_rename_merge_converged(tree1, 'file')
1129
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
1130
    def test_last_modified_revision_after_converged_merge_file_unchanged_ric(self):
1131
        # merge a file that changed preserves the last modified.
3775.2.23 by Robert Collins
CommitBuilder.record_iter_changes handles changed-in-branch files.
1132
        tree1 = self.make_branch_and_tree('t1')
1133
        self.build_tree(['t1/file'])
1134
        self._commit_sprout_rename_merge_converged(tree1, 'file',
1135
            mini_commit=self.mini_commit_record_iter_changes)
1136
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
1137
    def test_last_modified_revision_after_converged_merge_link_unchanged(self):
1138
        # merge a link that changed preserves the last modified.
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1139
        self.requireFeature(tests.SymlinkFeature)
1140
        tree1 = self.make_branch_and_tree('t1')
1141
        os.symlink('target', 't1/link')
1142
        self._commit_sprout_rename_merge_converged(tree1, 'link')
1143
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
1144
    def test_last_modified_revision_after_converged_merge_link_unchanged_ric(self):
1145
        # merge a link that changed preserves the last modified.
3775.2.24 by Robert Collins
CommitBuilder.record_iter_changes handles changed-in-branch symlinks.
1146
        self.requireFeature(tests.SymlinkFeature)
1147
        tree1 = self.make_branch_and_tree('t1')
1148
        os.symlink('target', 't1/link')
1149
        self._commit_sprout_rename_merge_converged(tree1, 'link',
1150
            mini_commit=self.mini_commit_record_iter_changes)
1151
3775.2.33 by Robert Collins
Fix bug with merges of new files, increasing test coverage to ensure its kept fixed.
1152
    def test_last_modified_revision_after_merge_new_dir_unchanged(self):
1153
        # merge a new dir does not change the last modified.
1154
        tree1 = self.make_branch_and_tree('t1')
1155
        self._commit_sprout_make_merge(tree1, self.make_dir)
1156
1157
    def test_last_modified_revision_after_merge_new_dir_unchanged_ric(self):
1158
        # merge a new dir does not change the last modified.
1159
        tree1 = self.make_branch_and_tree('t1')
1160
        self._commit_sprout_make_merge(tree1, self.make_dir,
1161
            mini_commit=self.mini_commit_record_iter_changes)
1162
1163
    def test_last_modified_revision_after_merge_new_file_unchanged(self):
1164
        # merge a new file does not change the last modified.
1165
        tree1 = self.make_branch_and_tree('t1')
1166
        self._commit_sprout_make_merge(tree1, self.make_file)
1167
1168
    def test_last_modified_revision_after_merge_new_file_unchanged_ric(self):
1169
        # merge a new file does not change the last modified.
1170
        tree1 = self.make_branch_and_tree('t1')
1171
        self._commit_sprout_make_merge(tree1, self.make_file,
1172
            mini_commit=self.mini_commit_record_iter_changes)
1173
1174
    def test_last_modified_revision_after_merge_new_link_unchanged(self):
1175
        # merge a new link does not change the last modified.
1176
        tree1 = self.make_branch_and_tree('t1')
1177
        self._commit_sprout_make_merge(tree1, self.make_link)
1178
1179
    def test_last_modified_revision_after_merge_new_link_unchanged_ric(self):
1180
        # merge a new link does not change the last modified.
1181
        tree1 = self.make_branch_and_tree('t1')
1182
        self._commit_sprout_make_merge(tree1, self.make_link,
1183
            mini_commit=self.mini_commit_record_iter_changes)
1184
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1185
    def make_dir(self, name):
1186
        self.build_tree([name + '/'])
1187
1188
    def make_file(self, name):
1189
        self.build_tree([name])
1190
1191
    def make_link(self, name):
1192
        self.requireFeature(tests.SymlinkFeature)
1193
        os.symlink('target', name)
1194
4183.5.2 by Robert Collins
Support tree-reference in record_iter_changes.
1195
    def make_reference(self, name):
1196
        tree = self.make_branch_and_tree(name, format='1.9-rich-root')
1197
        tree.commit('foo')
1198
        return tree
1199
3775.2.25 by Robert Collins
CommitBuilder.record_iter_changes handles directories becoming files and links.
1200
    def _check_kind_change(self, make_before, make_after, expect_fs_hash=False,
1201
        mini_commit=None):
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1202
        tree = self.make_branch_and_tree('.')
1203
        path = 'name'
1204
        make_before(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
1205
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1206
        def change_kind():
2831.5.2 by Vincent Ladeuil
Review feedback.
1207
            osutils.delete_any(path)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1208
            make_after(path)
2831.5.2 by Vincent Ladeuil
Review feedback.
1209
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
1210
        self._add_commit_change_check_changed(tree, path, change_kind,
3775.2.25 by Robert Collins
CommitBuilder.record_iter_changes handles directories becoming files and links.
1211
            expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1212
1213
    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.
1214
        self._check_kind_change(self.make_dir, self.make_file,
1215
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1216
3775.2.25 by Robert Collins
CommitBuilder.record_iter_changes handles directories becoming files and links.
1217
    def test_last_modified_dir_file_ric(self):
1218
        self._check_kind_change(self.make_dir, self.make_file,
1219
            expect_fs_hash=True,
1220
            mini_commit=self.mini_commit_record_iter_changes)
1221
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1222
    def test_last_modified_dir_link(self):
1223
        self._check_kind_change(self.make_dir, self.make_link)
1224
3775.2.25 by Robert Collins
CommitBuilder.record_iter_changes handles directories becoming files and links.
1225
    def test_last_modified_dir_link_ric(self):
1226
        self._check_kind_change(self.make_dir, self.make_link,
1227
            mini_commit=self.mini_commit_record_iter_changes)
1228
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1229
    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.
1230
        self._check_kind_change(self.make_link, self.make_file,
1231
            expect_fs_hash=True)
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1232
3775.2.26 by Robert Collins
CommitBuilder.record_iter_changes handles links becomes directories and files.
1233
    def test_last_modified_link_file_ric(self):
1234
        self._check_kind_change(self.make_link, self.make_file,
1235
            expect_fs_hash=True,
1236
            mini_commit=self.mini_commit_record_iter_changes)
1237
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1238
    def test_last_modified_link_dir(self):
1239
        self._check_kind_change(self.make_link, self.make_dir)
1240
3775.2.26 by Robert Collins
CommitBuilder.record_iter_changes handles links becomes directories and files.
1241
    def test_last_modified_link_dir_ric(self):
1242
        self._check_kind_change(self.make_link, self.make_dir,
1243
            mini_commit=self.mini_commit_record_iter_changes)
1244
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1245
    def test_last_modified_file_dir(self):
1246
        self._check_kind_change(self.make_file, self.make_dir)
1247
3775.2.27 by Robert Collins
CommitBuilder.record_iter_changes handles files becoming directories and links.
1248
    def test_last_modified_file_dir_ric(self):
1249
        self._check_kind_change(self.make_file, self.make_dir,
1250
            mini_commit=self.mini_commit_record_iter_changes)
1251
2776.1.5 by Robert Collins
Add reasonably comprehensive tests for path last modified and per file graph behaviour.
1252
    def test_last_modified_file_link(self):
1253
        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'.
1254
3775.2.27 by Robert Collins
CommitBuilder.record_iter_changes handles files becoming directories and links.
1255
    def test_last_modified_file_link_ric(self):
1256
        self._check_kind_change(self.make_file, self.make_link,
1257
            mini_commit=self.mini_commit_record_iter_changes)
3775.2.28 by Robert Collins
Merge .dev.
1258
3831.1.1 by John Arbash Meinel
Before allowing commit to succeed, verify the texts will be 'safe'.
1259
    def test_get_commit_builder_with_invalid_revprops(self):
1260
        branch = self.make_branch('.')
1261
        branch.repository.lock_write()
1262
        self.addCleanup(branch.repository.unlock)
1263
        self.assertRaises(ValueError, branch.repository.get_commit_builder,
1264
            branch, [], branch.get_config(),
1265
            revprops={'invalid': u'property\rwith\r\ninvalid chars'})
1266
1267
    def test_commit_builder_commit_with_invalid_message(self):
1268
        branch = self.make_branch('.')
1269
        branch.repository.lock_write()
1270
        self.addCleanup(branch.repository.unlock)
1271
        builder = branch.repository.get_commit_builder(branch, [],
1272
            branch.get_config())
1273
        self.addCleanup(branch.repository.abort_write_group)
1274
        self.assertRaises(ValueError, builder.commit,
1275
            u'Invalid\r\ncommit message\r\n')
4595.4.2 by Robert Collins
Disable commit builders on stacked repositories.
1276
1277
    def test_stacked_repositories_reject_commit_builder(self):
1278
        # As per bug 375013, committing to stacked repositories is currently
1279
        # broken, so repositories with fallbacks refuse to hand out a commit
1280
        # builder.
1281
        repo_basis = self.make_repository('basis')
1282
        branch = self.make_branch('local')
1283
        repo_local = branch.repository
1284
        try:
1285
            repo_local.add_fallback_repository(repo_basis)
1286
        except errors.UnstackableRepositoryFormat:
1287
            raise tests.TestNotApplicable("not a stackable format.")
1288
        repo_local.lock_write()
1289
        self.addCleanup(repo_local.unlock)
1290
        self.assertRaises(errors.BzrError, repo_local.get_commit_builder,
1291
            branch, [], branch.get_config())