~bzr/ubuntu/lucid/bzr/beta-ppa

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_repository/test_commit_builder.py

  • Committer: Martin Pool
  • Date: 2010-07-02 07:29:40 UTC
  • mfrom: (129.1.7 packaging-karmic)
  • Revision ID: mbp@sourcefrog.net-20100702072940-hpzq5elg8wjve8rh
* PPA rebuild.
* PPA rebuild for Karmic.
* PPA rebuild for Jaunty.
* PPA rebuild for Hardy.
* From postinst, actually remove the example bash completion scripts.
  (LP: #249452)
* New upstream release.
* New upstream release.
* New upstream release.
* Revert change to Build-depends: Dapper does not have python-central.
  Should be python-support..
* Target ppa..
* Target ppa..
* Target ppa..
* Target ppa..
* New upstream release.
* Switch to dpkg-source 3.0 (quilt) format.
* Bump standards version to 3.8.4.
* Remove embedded copy of python-configobj. Closes: #555336
* Remove embedded copy of python-elementtree. Closes: #555343
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* debian/control: Fix obsolete-relation-form-in-source
  lintian warning. 
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split out docs into bzr-doc package.
* New upstream release.
* Added John Francesco Ferlito to Uploaders.
* Fix install path to quick-reference guide
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again, again.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to path changes.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Bump standards version to 3.8.3.
* Remove unused patch system.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix copy and paste tab error in .install file
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
 + Fixes compatibility with Python 2.4. Closes: #537708
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream version.
* Bump standards version to 3.8.2.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add python-pyrex to build-deps to ensure C extensions are always build.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix API compatibility version. (Closes: #526233)
* New upstream release.
  + Fixes default format for upgrade command. (Closes: #464688)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add missing dependency on zlib development library. (Closes:
  #523595)
* Add zlib build-depends.
* Add zlib build-depends.
* Add zlib build-depends.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Move to section vcs.
* Bump standards version to 3.8.1.
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Recommend ca-certificates. (Closes: #452024)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Update watch file. bazaar now uses launchpad to host its sources.
* Remove patch for inventory root revision copy, applied upstream.
* New upstream release.
* New upstream release.
* New upstream release
* Force removal of files installed in error to /etc/bash_completion.d/
  (LP: #249452)
* New upstream release.
* New upstream release
* New upstream release.
* Bump standards version.
* Include patch for inventory root revision copy, required for bzr-svn.
* New upstream release.
* Remove unused lintian overrides.
* Correct the package version not to be native.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Final 1.5 release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add myself as a co-maintainer.
* Add a Dm-Upload-Allowed: yes header.
* New upstream bugfix release.
* New upstream release.
* Final 1.3 release.
* New upstream release.
* First release candidate of the upcoming 1.3 release.
* Rebuild to fix the problem caused by a build with a broken python-central.
* New upstream release.
* Rebuild for dapper PPA.
* Apply Lamont's patches to fix build-dependencies on dapper.
  (See: https://bugs.launchpad.net/bzr/+bug/189915)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Tests for repository commit builder."""
 
18
 
 
19
from copy import copy
 
20
import errno
 
21
import os
 
22
import sys
 
23
 
 
24
from bzrlib import (
 
25
    errors,
 
26
    inventory,
 
27
    osutils,
 
28
    repository,
 
29
    revision as _mod_revision,
 
30
    tests,
 
31
    )
 
32
from bzrlib.graph import Graph
 
33
from bzrlib.tests.per_repository import test_repository
 
34
 
 
35
 
 
36
class TestCommitBuilder(test_repository.TestCaseWithRepository):
 
37
 
 
38
    def test_get_commit_builder(self):
 
39
        branch = self.make_branch('.')
 
40
        branch.repository.lock_write()
 
41
        builder = branch.repository.get_commit_builder(
 
42
            branch, [], branch.get_config())
 
43
        self.assertIsInstance(builder, repository.CommitBuilder)
 
44
        self.assertTrue(builder.random_revid)
 
45
        branch.repository.commit_write_group()
 
46
        branch.repository.unlock()
 
47
 
 
48
    def record_root(self, builder, tree):
 
49
        if builder.record_root_entry is True:
 
50
            tree.lock_read()
 
51
            try:
 
52
                ie = tree.inventory.root
 
53
            finally:
 
54
                tree.unlock()
 
55
            parent_tree = tree.branch.repository.revision_tree(
 
56
                              _mod_revision.NULL_REVISION)
 
57
            parent_invs = []
 
58
            builder.record_entry_contents(ie, parent_invs, '', tree,
 
59
                tree.path_content_summary(''))
 
60
 
 
61
    def test_finish_inventory_with_record_root(self):
 
62
        tree = self.make_branch_and_tree(".")
 
63
        tree.lock_write()
 
64
        try:
 
65
            builder = tree.branch.get_commit_builder([])
 
66
            repo = tree.branch.repository
 
67
            self.record_root(builder, tree)
 
68
            builder.finish_inventory()
 
69
            repo.commit_write_group()
 
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:
 
79
                list(builder.record_iter_changes(tree, tree.last_revision(),
 
80
                    tree.iter_changes(tree.basis_tree())))
 
81
                builder.finish_inventory()
 
82
            except:
 
83
                builder.abort()
 
84
                raise
 
85
            repo = tree.branch.repository
 
86
            repo.commit_write_group()
 
87
        finally:
 
88
            tree.unlock()
 
89
 
 
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
 
 
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:
 
107
                basis = tree.basis_tree()
 
108
                last_rev = tree.last_revision()
 
109
                changes = tree.iter_changes(basis)
 
110
                list(builder.record_iter_changes(tree, last_rev, changes))
 
111
                builder.finish_inventory()
 
112
            finally:
 
113
                builder.abort()
 
114
        finally:
 
115
            tree.unlock()
 
116
 
 
117
    def test_commit_message(self):
 
118
        tree = self.make_branch_and_tree(".")
 
119
        tree.lock_write()
 
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()
 
127
        rev = tree.branch.repository.get_revision(rev_id)
 
128
        self.assertEqual('foo bar blah', rev.message)
 
129
 
 
130
    def test_commit_with_revision_id(self):
 
131
        tree = self.make_branch_and_tree(".")
 
132
        tree.lock_write()
 
133
        try:
 
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')
 
137
            try:
 
138
                try:
 
139
                    builder = tree.branch.get_commit_builder([],
 
140
                        revision_id=revision_id)
 
141
                except errors.NonAsciiRevisionId:
 
142
                    revision_id = 'abc'
 
143
                    builder = tree.branch.get_commit_builder([],
 
144
                        revision_id=revision_id)
 
145
            except errors.CannotSetRevisionId:
 
146
                # This format doesn't support supplied revision ids
 
147
                return
 
148
            self.assertFalse(builder.random_revid)
 
149
            self.record_root(builder, tree)
 
150
            builder.finish_inventory()
 
151
            self.assertEqual(revision_id, builder.commit('foo bar'))
 
152
        finally:
 
153
            tree.unlock()
 
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)
 
161
 
 
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:
 
182
                list(builder.record_iter_changes(tree, tree.last_revision(),
 
183
                    tree.iter_changes(tree.basis_tree())))
 
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
 
 
199
    def test_commit_without_root_or_record_iter_changes_errors(self):
 
200
        tree = self.make_branch_and_tree(".")
 
201
        tree.lock_write()
 
202
        try:
 
203
            self.build_tree(['foo'])
 
204
            tree.add('foo', 'foo-id')
 
205
            entry = tree.inventory['foo-id']
 
206
            builder = tree.branch.get_commit_builder([])
 
207
            self.assertRaises(errors.RootMissing,
 
208
                builder.record_entry_contents, entry, [], 'foo', tree,
 
209
                    tree.path_content_summary('foo'))
 
210
            builder.abort()
 
211
        finally:
 
212
            tree.unlock()
 
213
 
 
214
    def test_commit_unchanged_root(self):
 
215
        tree = self.make_branch_and_tree(".")
 
216
        old_revision_id = tree.commit('')
 
217
        tree.lock_write()
 
218
        parent_tree = tree.basis_tree()
 
219
        parent_tree.lock_read()
 
220
        self.addCleanup(parent_tree.unlock)
 
221
        builder = tree.branch.get_commit_builder([old_revision_id])
 
222
        try:
 
223
            ie = inventory.make_entry('directory', '', None,
 
224
                    tree.get_root_id())
 
225
            delta, version_recorded, fs_hash = builder.record_entry_contents(
 
226
                ie, [parent_tree.inventory], '', tree,
 
227
                tree.path_content_summary(''))
 
228
            # Regardless of repository root behaviour we should consider this a
 
229
            # pointless commit.
 
230
            self.assertFalse(builder.any_changes())
 
231
            self.assertFalse(version_recorded)
 
232
            # if the repository format recorded a new root revision, that
 
233
            # should be in the delta
 
234
            got_new_revision = ie.revision != old_revision_id
 
235
            if got_new_revision:
 
236
                self.assertEqual(('', '', ie.file_id, ie), delta)
 
237
                # The delta should be tracked
 
238
                self.assertEqual(delta, builder._basis_delta[-1])
 
239
            else:
 
240
                self.assertEqual(None, delta)
 
241
            # Directories do not get hashed.
 
242
            self.assertEqual(None, fs_hash)
 
243
            builder.abort()
 
244
        except:
 
245
            builder.abort()
 
246
            tree.unlock()
 
247
            raise
 
248
        else:
 
249
            tree.unlock()
 
250
 
 
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()
 
255
        builder = tree.branch.get_commit_builder([old_revision_id])
 
256
        try:
 
257
            list(builder.record_iter_changes(tree, old_revision_id, []))
 
258
            # Regardless of repository root behaviour we should consider this a
 
259
            # pointless commit.
 
260
            self.assertFalse(builder.any_changes())
 
261
            builder.finish_inventory()
 
262
            new_root = tree.branch.repository.get_inventory(
 
263
                builder._new_revision_id).root
 
264
            if tree.branch.repository.supports_rich_root():
 
265
                # We should not have seen a new root revision
 
266
                self.assertEqual(old_revision_id, new_root.revision)
 
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
 
 
274
    def test_commit(self):
 
275
        tree = self.make_branch_and_tree(".")
 
276
        tree.lock_write()
 
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()
 
284
        self.assertNotEqual(None, rev_id)
 
285
        self.assertTrue(tree.branch.repository.has_revision(rev_id))
 
286
        # the revision id must be set on the inventory when saving it. This does not
 
287
        # precisely test that - a repository that wants to can add it on deserialisation,
 
288
        # but thats all the current contract guarantees anyway.
 
289
        self.assertEqual(rev_id, tree.branch.repository.get_inventory(rev_id).revision_id)
 
290
 
 
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
 
 
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:
 
361
                builder.will_record_deletes()
 
362
                if builder.record_root_entry is True:
 
363
                    parent_invs = [basis.inventory]
 
364
                    del basis.inventory.root.children['foo']
 
365
                    builder.record_entry_contents(basis.inventory.root,
 
366
                        parent_invs, '', tree, tree.path_content_summary(''))
 
367
                # the delta should be returned, and recorded in _basis_delta
 
368
                delta = builder.record_delete("foo", "foo-id")
 
369
                self.assertEqual(("foo", None, "foo-id", None), delta)
 
370
                self.assertEqual(delta, builder._basis_delta[-1])
 
371
                builder.finish_inventory()
 
372
                rev_id2 = builder.commit('delete foo')
 
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
 
 
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))
 
395
                list(builder.record_iter_changes(tree, rev_id,
 
396
                    [delete_change]))
 
397
                self.assertEqual(("foo", None, "foo-id", None),
 
398
                    builder._basis_delta[0])
 
399
                self.assertTrue(builder.any_changes())
 
400
                builder.finish_inventory()
 
401
                rev_id2 = builder.commit('delete foo')
 
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
 
 
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
 
 
429
    def test_revision_tree(self):
 
430
        tree = self.make_branch_and_tree(".")
 
431
        tree.lock_write()
 
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()
 
439
        rev_tree = builder.revision_tree()
 
440
        # Just a couple simple tests to ensure that it actually follows
 
441
        # the RevisionTree api.
 
442
        self.assertEqual(rev_id, rev_tree.get_revision_id())
 
443
        self.assertEqual([], rev_tree.get_parent_ids())
 
444
 
 
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:
 
451
                list(builder.record_iter_changes(tree,
 
452
                    _mod_revision.NULL_REVISION,
 
453
                    tree.iter_changes(tree.basis_tree())))
 
454
                builder.finish_inventory()
 
455
                rev_id = builder.commit('foo bar')
 
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
 
 
467
    def test_root_entry_has_revision(self):
 
468
        # test the root revision created and put in the basis
 
469
        # has the right rev id.
 
470
        # XXX: RBC 20081118 - this test is too big, it depends on the exact
 
471
        # behaviour of tree methods and so on; it should be written to the
 
472
        # commit builder interface directly.
 
473
        tree = self.make_branch_and_tree('.')
 
474
        rev_id = tree.commit('message')
 
475
        basis_tree = tree.basis_tree()
 
476
        basis_tree.lock_read()
 
477
        self.addCleanup(basis_tree.unlock)
 
478
        self.assertEqual(rev_id, basis_tree.inventory.root.revision)
 
479
 
 
480
    def _get_revtrees(self, tree, revision_ids):
 
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()
 
490
 
 
491
    def test_last_modified_revision_after_commit_root_unchanged(self):
 
492
        # commiting without changing the root does not change the
 
493
        # last modified except on non-rich-root-repositories.
 
494
        tree = self.make_branch_and_tree('.')
 
495
        rev1 = tree.commit('')
 
496
        rev2 = tree.commit('')
 
497
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
 
498
        self.assertEqual(rev1, tree1.inventory.root.revision)
 
499
        if tree.branch.repository.supports_rich_root():
 
500
            self.assertEqual(rev1, tree2.inventory.root.revision)
 
501
        else:
 
502
            self.assertEqual(rev2, tree2.inventory.root.revision)
 
503
 
 
504
    def _add_commit_check_unchanged(self, tree, name, mini_commit=None):
 
505
        tree.add([name], [name + 'id'])
 
506
        self._commit_check_unchanged(tree, name, name + 'id',
 
507
            mini_commit=mini_commit)
 
508
 
 
509
    def _commit_check_unchanged(self, tree, name, file_id, mini_commit=None):
 
510
        rev1 = tree.commit('')
 
511
        if mini_commit is None:
 
512
            mini_commit = self.mini_commit
 
513
        rev2 = mini_commit(tree, name, name, False, False)
 
514
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
 
515
        self.assertEqual(rev1, tree1.inventory[file_id].revision)
 
516
        self.assertEqual(rev1, tree2.inventory[file_id].revision)
 
517
        expected_graph = {}
 
518
        expected_graph[(file_id, rev1)] = ()
 
519
        self.assertFileGraph(expected_graph, tree, (file_id, rev1))
 
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
 
 
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
 
 
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)
 
547
        file_id = 'dirid'
 
548
        expected_graph = {}
 
549
        expected_graph[(file_id, rev1)] = ()
 
550
        self.assertFileGraph(expected_graph, tree, (file_id, rev1))
 
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
 
 
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
 
 
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
 
 
572
    def test_last_modified_revision_after_commit_link_unchanged_ric(self):
 
573
        # committing without changing a link does not change the last modified.
 
574
        self.requireFeature(tests.SymlinkFeature)
 
575
        tree = self.make_branch_and_tree('.')
 
576
        os.symlink('target', 'link')
 
577
        self._add_commit_check_unchanged(tree, 'link',
 
578
            mini_commit=self.mini_commit_record_iter_changes)
 
579
 
 
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
 
 
605
    def _add_commit_renamed_check_changed(self, tree, name,
 
606
        expect_fs_hash=False, mini_commit=None):
 
607
        def rename():
 
608
            tree.rename_one(name, 'new_' + name)
 
609
        self._add_commit_change_check_changed(tree, name, rename,
 
610
            expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
 
611
 
 
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
 
 
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
 
 
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
 
 
632
    def test_last_modified_revision_after_rename_file_changes(self):
 
633
        # renaming a file changes the last modified.
 
634
        tree = self.make_branch_and_tree('.')
 
635
        self.build_tree(['file'])
 
636
        self._add_commit_renamed_check_changed(tree, 'file',
 
637
            expect_fs_hash=True)
 
638
 
 
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
 
 
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
 
 
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
 
 
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
 
 
685
    def _add_commit_reparent_check_changed(self, tree, name,
 
686
        expect_fs_hash=False, mini_commit=None):
 
687
        self.build_tree(['newparent/'])
 
688
        tree.add(['newparent'])
 
689
        def reparent():
 
690
            tree.rename_one(name, 'newparent/new_' + name)
 
691
        self._add_commit_change_check_changed(tree, name, reparent,
 
692
            expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
 
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
 
 
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
 
 
707
    def test_last_modified_revision_after_reparent_file_changes(self):
 
708
        # reparenting a file changes the last modified.
 
709
        tree = self.make_branch_and_tree('.')
 
710
        self.build_tree(['file'])
 
711
        self._add_commit_reparent_check_changed(tree, 'file',
 
712
            expect_fs_hash=True)
 
713
 
 
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',
 
719
            expect_fs_hash=True,
 
720
            mini_commit=self.mini_commit_record_iter_changes)
 
721
 
 
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
 
 
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
 
 
737
    def _add_commit_change_check_changed(self, tree, name, changer,
 
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,
 
744
            changer, expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
 
745
 
 
746
    def _commit_change_check_changed(self, tree, name, file_id, changer,
 
747
        expect_fs_hash=False, mini_commit=None):
 
748
        rev1 = tree.commit('')
 
749
        changer()
 
750
        if mini_commit is None:
 
751
            mini_commit = self.mini_commit
 
752
        rev2 = mini_commit(tree, name, tree.id2path(file_id),
 
753
            expect_fs_hash=expect_fs_hash)
 
754
        tree1, tree2 = self._get_revtrees(tree, [rev1, rev2])
 
755
        self.assertEqual(rev1, tree1.inventory[file_id].revision)
 
756
        self.assertEqual(rev2, tree2.inventory[file_id].revision)
 
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))
 
761
 
 
762
    def mini_commit(self, tree, name, new_name, records_version=True,
 
763
        delta_against_basis=True, expect_fs_hash=False):
 
764
        """Perform a miniature commit looking for record entry results.
 
765
 
 
766
        :param tree: The tree to commit.
 
767
        :param name: The path in the basis tree of the tree being committed.
 
768
        :param new_name: The path in the tree being committed.
 
769
        :param records_version: True if the commit of new_name is expected to
 
770
            record a new version.
 
771
        :param delta_against_basis: True of the commit of new_name is expected
 
772
            to have a delta against the basis.
 
773
        :param expect_fs_hash: True or false to indicate whether we expect a
 
774
            file hash to be returned from the record_entry_contents call.
 
775
        """
 
776
        tree.lock_write()
 
777
        try:
 
778
            # mini manual commit here so we can check the return of
 
779
            # record_entry_contents.
 
780
            parent_ids = tree.get_parent_ids()
 
781
            builder = tree.branch.get_commit_builder(parent_ids)
 
782
            parent_tree = tree.basis_tree()
 
783
            parent_tree.lock_read()
 
784
            self.addCleanup(parent_tree.unlock)
 
785
            parent_invs = [parent_tree.inventory]
 
786
            for parent_id in parent_ids[1:]:
 
787
                parent_invs.append(tree.branch.repository.revision_tree(
 
788
                    parent_id).inventory)
 
789
            # root
 
790
            builder.record_entry_contents(
 
791
                inventory.make_entry('directory', '', None,
 
792
                    tree.get_root_id()), parent_invs, '', tree,
 
793
                    tree.path_content_summary(''))
 
794
            def commit_id(file_id):
 
795
                old_ie = tree.inventory[file_id]
 
796
                path = tree.id2path(file_id)
 
797
                ie = inventory.make_entry(tree.kind(file_id), old_ie.name,
 
798
                    old_ie.parent_id, file_id)
 
799
                content_summary = tree.path_content_summary(path)
 
800
                if content_summary[0] == 'tree-reference':
 
801
                    content_summary = content_summary[:3] + (
 
802
                        tree.get_reference_revision(file_id),)
 
803
                return builder.record_entry_contents(ie, parent_invs, path,
 
804
                    tree, content_summary)
 
805
 
 
806
            file_id = tree.path2id(new_name)
 
807
            parent_id = tree.inventory[file_id].parent_id
 
808
            if parent_id != tree.get_root_id():
 
809
                commit_id(parent_id)
 
810
            # because a change of some sort is meant to have occurred,
 
811
            # recording the entry must return True.
 
812
            delta, version_recorded, fs_hash = commit_id(file_id)
 
813
            if records_version:
 
814
                self.assertTrue(version_recorded)
 
815
            else:
 
816
                self.assertFalse(version_recorded)
 
817
            if expect_fs_hash:
 
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])
 
823
            else:
 
824
                self.assertEqual(None, fs_hash)
 
825
            new_entry = builder.new_inventory[file_id]
 
826
            if delta_against_basis:
 
827
                expected_delta = (name, new_name, file_id, new_entry)
 
828
                # The delta should be recorded
 
829
                self.assertEqual(expected_delta, builder._basis_delta[-1])
 
830
            else:
 
831
                expected_delta = None
 
832
            self.assertEqual(expected_delta, delta)
 
833
            builder.finish_inventory()
 
834
            rev2 = builder.commit('')
 
835
        except:
 
836
            builder.abort()
 
837
            tree.unlock()
 
838
            raise
 
839
        try:
 
840
            tree.set_parent_ids([rev2])
 
841
        finally:
 
842
            tree.unlock()
 
843
        return rev2
 
844
 
 
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.
 
858
        :param expect_fs_hash: If true, looks for a fs hash output from
 
859
            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))
 
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)
 
888
            delta = builder._basis_delta
 
889
            delta_dict = dict((change[2], change) for change in delta)
 
890
            version_recorded = (file_id in delta_dict and
 
891
                delta_dict[file_id][3] is not None and
 
892
                delta_dict[file_id][3].revision == builder._new_revision_id)
 
893
            if records_version:
 
894
                self.assertTrue(version_recorded)
 
895
            else:
 
896
                self.assertFalse(version_recorded)
 
897
            self.assertIs(None, builder.new_inventory)
 
898
            builder.finish_inventory()
 
899
            inv_key = (builder._new_revision_id,)
 
900
            inv_sha1 = tree.branch.repository.inventories.get_sha1s(
 
901
                            [inv_key])[inv_key]
 
902
            self.assertEqual(inv_sha1, builder.inv_sha1)
 
903
            self.assertIs(None, builder.new_inventory)
 
904
            new_inventory = builder.revision_tree().inventory
 
905
            new_entry = new_inventory[file_id]
 
906
            if delta_against_basis:
 
907
                expected_delta = (name, new_name, file_id, new_entry)
 
908
                self.assertEqual(expected_delta, delta_dict[file_id])
 
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
 
 
922
    def assertFileGraph(self, expected_graph, tree, tip):
 
923
        # all the changes that have occured should be in the ancestry
 
924
        # (closest to a public per-file graph API we have today)
 
925
        tree.lock_read()
 
926
        self.addCleanup(tree.unlock)
 
927
        graph = dict(Graph(tree.branch.repository.texts).iter_ancestry([tip]))
 
928
        self.assertEqual(expected_graph, graph)
 
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')
 
936
        self._add_commit_change_check_changed(tree, 'file', change_file,
 
937
            expect_fs_hash=True)
 
938
 
 
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
 
 
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
 
 
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
 
963
        # changing a link changes the last modified.
 
964
        self.requireFeature(tests.SymlinkFeature)
 
965
        tree = self.make_branch_and_tree('.')
 
966
        os.symlink(target, link)
 
967
        def change_link():
 
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'))
 
985
 
 
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
 
 
995
    def _commit_sprout_rename_merge(self, tree1, name, expect_fs_hash=False,
 
996
        mini_commit=None):
 
997
        """Do a rename in both trees."""
 
998
        rev1, tree2 = self._commit_sprout(tree1, name)
 
999
        # change both sides equally
 
1000
        rev2 = self._rename_in_tree(tree1, name)
 
1001
        rev3 = self._rename_in_tree(tree2, name)
 
1002
        tree1.merge_from_branch(tree2.branch)
 
1003
        if mini_commit is None:
 
1004
            mini_commit = self.mini_commit
 
1005
        rev4 = mini_commit(tree1, 'new_' + name, 'new_' + name,
 
1006
            expect_fs_hash=expect_fs_hash)
 
1007
        tree3, = self._get_revtrees(tree1, [rev4])
 
1008
        self.assertEqual(rev4, tree3.inventory[name + 'id'].revision)
 
1009
        file_id = name + 'id'
 
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))
 
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
 
 
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
 
 
1030
    def test_last_modified_revision_after_merge_file_changes(self):
 
1031
        # merge a file changes the last modified.
 
1032
        tree1 = self.make_branch_and_tree('t1')
 
1033
        self.build_tree(['t1/file'])
 
1034
        self._commit_sprout_rename_merge(tree1, 'file', expect_fs_hash=True)
 
1035
 
 
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
 
 
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
 
 
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
 
 
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.
 
1063
        # Part 1: change in the merged branch.
 
1064
        rev1, tree2 = self._commit_sprout(tree1, name)
 
1065
        # change on the other side to merge back
 
1066
        rev2 = self._rename_in_tree(tree2, name)
 
1067
        tree1.merge_from_branch(tree2.branch)
 
1068
        if mini_commit is None:
 
1069
            mini_commit = self.mini_commit
 
1070
        def _check_graph(in_tree, changed_in_tree):
 
1071
            rev3 = mini_commit(in_tree, name, 'new_' + name, False,
 
1072
                delta_against_basis=changed_in_tree)
 
1073
            tree3, = self._get_revtrees(in_tree, [rev2])
 
1074
            self.assertEqual(rev2, tree3.inventory[name + 'id'].revision)
 
1075
            file_id = name + 'id'
 
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)
 
1088
 
 
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.
 
1113
        tree1 = self.make_branch_and_tree('t1')
 
1114
        self.build_tree(['t1/dir/'])
 
1115
        self._commit_sprout_rename_merge_converged(tree1, 'dir')
 
1116
 
 
1117
    def test_last_modified_revision_after_converged_merge_dir_unchanged_ric(self):
 
1118
        # merge a dir that changed preserves the last modified.
 
1119
        tree1 = self.make_branch_and_tree('t1')
 
1120
        self.build_tree(['t1/dir/'])
 
1121
        self._commit_sprout_rename_merge_converged(tree1, 'dir',
 
1122
            mini_commit=self.mini_commit_record_iter_changes)
 
1123
 
 
1124
    def test_last_modified_revision_after_converged_merge_file_unchanged(self):
 
1125
        # merge a file that changed preserves the last modified.
 
1126
        tree1 = self.make_branch_and_tree('t1')
 
1127
        self.build_tree(['t1/file'])
 
1128
        self._commit_sprout_rename_merge_converged(tree1, 'file')
 
1129
 
 
1130
    def test_last_modified_revision_after_converged_merge_file_unchanged_ric(self):
 
1131
        # merge a file that changed preserves the last modified.
 
1132
        tree1 = self.make_branch_and_tree('t1')
 
1133
        self.build_tree(['t1/file'])
 
1134
        self._commit_sprout_rename_merge_converged(tree1, 'file',
 
1135
            mini_commit=self.mini_commit_record_iter_changes)
 
1136
 
 
1137
    def test_last_modified_revision_after_converged_merge_link_unchanged(self):
 
1138
        # merge a link that changed preserves the last modified.
 
1139
        self.requireFeature(tests.SymlinkFeature)
 
1140
        tree1 = self.make_branch_and_tree('t1')
 
1141
        os.symlink('target', 't1/link')
 
1142
        self._commit_sprout_rename_merge_converged(tree1, 'link')
 
1143
 
 
1144
    def test_last_modified_revision_after_converged_merge_link_unchanged_ric(self):
 
1145
        # merge a link that changed preserves the last modified.
 
1146
        self.requireFeature(tests.SymlinkFeature)
 
1147
        tree1 = self.make_branch_and_tree('t1')
 
1148
        os.symlink('target', 't1/link')
 
1149
        self._commit_sprout_rename_merge_converged(tree1, 'link',
 
1150
            mini_commit=self.mini_commit_record_iter_changes)
 
1151
 
 
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
 
 
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
 
 
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
 
 
1200
    def _check_kind_change(self, make_before, make_after, expect_fs_hash=False,
 
1201
        mini_commit=None):
 
1202
        tree = self.make_branch_and_tree('.')
 
1203
        path = 'name'
 
1204
        make_before(path)
 
1205
 
 
1206
        def change_kind():
 
1207
            osutils.delete_any(path)
 
1208
            make_after(path)
 
1209
 
 
1210
        self._add_commit_change_check_changed(tree, path, change_kind,
 
1211
            expect_fs_hash=expect_fs_hash, mini_commit=mini_commit)
 
1212
 
 
1213
    def test_last_modified_dir_file(self):
 
1214
        self._check_kind_change(self.make_dir, self.make_file,
 
1215
            expect_fs_hash=True)
 
1216
 
 
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
 
 
1222
    def test_last_modified_dir_link(self):
 
1223
        self._check_kind_change(self.make_dir, self.make_link)
 
1224
 
 
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
 
 
1229
    def test_last_modified_link_file(self):
 
1230
        self._check_kind_change(self.make_link, self.make_file,
 
1231
            expect_fs_hash=True)
 
1232
 
 
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
 
 
1238
    def test_last_modified_link_dir(self):
 
1239
        self._check_kind_change(self.make_link, self.make_dir)
 
1240
 
 
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
 
 
1245
    def test_last_modified_file_dir(self):
 
1246
        self._check_kind_change(self.make_file, self.make_dir)
 
1247
 
 
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
 
 
1252
    def test_last_modified_file_link(self):
 
1253
        self._check_kind_change(self.make_file, self.make_link)
 
1254
 
 
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)
 
1258
 
 
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')
 
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())