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

« back to all changes in this revision

Viewing changes to bzrlib/tests/per_workingtree/test_parents.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 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 of the parent related functions of WorkingTrees."""
 
18
 
 
19
from errno import EEXIST
 
20
import os
 
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    osutils,
 
25
    revision as _mod_revision,
 
26
    symbol_versioning,
 
27
    tests,
 
28
    )
 
29
from bzrlib.inventory import (
 
30
    Inventory,
 
31
    InventoryFile,
 
32
    InventoryDirectory,
 
33
    InventoryLink,
 
34
    )
 
35
from bzrlib.revision import Revision
 
36
from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
 
37
from bzrlib.uncommit import uncommit
 
38
 
 
39
 
 
40
class TestParents(TestCaseWithWorkingTree):
 
41
 
 
42
    def assertConsistentParents(self, expected, tree):
 
43
        """Check that the parents found are as expected.
 
44
 
 
45
        This test helper also checks that they are consistent with
 
46
        the pre-get_parent_ids() api - which is now deprecated.
 
47
        """
 
48
        self.assertEqual(expected, tree.get_parent_ids())
 
49
        if expected == []:
 
50
            self.assertEqual(_mod_revision.NULL_REVISION,
 
51
                             _mod_revision.ensure_null(tree.last_revision()))
 
52
        else:
 
53
            self.assertEqual(expected[0], tree.last_revision())
 
54
 
 
55
 
 
56
class TestGetParents(TestParents):
 
57
 
 
58
    def test_get_parents(self):
 
59
        t = self.make_branch_and_tree('.')
 
60
        self.assertEqual([], t.get_parent_ids())
 
61
 
 
62
 
 
63
class TestSetParents(TestParents):
 
64
 
 
65
    def test_set_no_parents(self):
 
66
        t = self.make_branch_and_tree('.')
 
67
        t.set_parent_trees([])
 
68
        self.assertEqual([], t.get_parent_ids())
 
69
        # now give it a real parent, and then set it to no parents again.
 
70
        t.commit('first post')
 
71
        t.set_parent_trees([])
 
72
        self.assertConsistentParents([], t)
 
73
 
 
74
    def test_set_null_parent(self):
 
75
        t = self.make_branch_and_tree('.')
 
76
        self.assertRaises(errors.ReservedId, t.set_parent_ids, ['null:'],
 
77
                          allow_leftmost_as_ghost=True)
 
78
        self.assertRaises(errors.ReservedId, t.set_parent_trees,
 
79
                          [('null:', None)], allow_leftmost_as_ghost=True)
 
80
 
 
81
    def test_set_one_ghost_parent_rejects(self):
 
82
        t = self.make_branch_and_tree('.')
 
83
        self.assertRaises(errors.GhostRevisionUnusableHere,
 
84
            t.set_parent_trees, [('missing-revision-id', None)])
 
85
 
 
86
    def test_set_one_ghost_parent_force(self):
 
87
        t = self.make_branch_and_tree('.')
 
88
        t.set_parent_trees([('missing-revision-id', None)],
 
89
            allow_leftmost_as_ghost=True)
 
90
        self.assertConsistentParents(['missing-revision-id'], t)
 
91
 
 
92
    def test_set_two_parents_one_ghost(self):
 
93
        t = self.make_branch_and_tree('.')
 
94
        revision_in_repo = t.commit('first post')
 
95
        # remove the tree's history
 
96
        uncommit(t.branch, tree=t)
 
97
        rev_tree = t.branch.repository.revision_tree(revision_in_repo)
 
98
        t.set_parent_trees([(revision_in_repo, rev_tree),
 
99
            ('another-missing', None)])
 
100
        self.assertConsistentParents([revision_in_repo, 'another-missing'], t)
 
101
 
 
102
    def test_set_three_parents(self):
 
103
        t = self.make_branch_and_tree('.')
 
104
        first_revision = t.commit('first post')
 
105
        uncommit(t.branch, tree=t)
 
106
        second_revision = t.commit('second post')
 
107
        uncommit(t.branch, tree=t)
 
108
        third_revision = t.commit('third post')
 
109
        uncommit(t.branch, tree=t)
 
110
        rev_tree1 = t.branch.repository.revision_tree(first_revision)
 
111
        rev_tree2 = t.branch.repository.revision_tree(second_revision)
 
112
        rev_tree3 = t.branch.repository.revision_tree(third_revision)
 
113
        t.set_parent_trees([(first_revision, rev_tree1),
 
114
            (second_revision, rev_tree2),
 
115
            (third_revision, rev_tree3)])
 
116
        self.assertConsistentParents(
 
117
            [first_revision, second_revision, third_revision], t)
 
118
 
 
119
    def test_set_no_parents_ids(self):
 
120
        t = self.make_branch_and_tree('.')
 
121
        t.set_parent_ids([])
 
122
        self.assertEqual([], t.get_parent_ids())
 
123
        # now give it a real parent, and then set it to no parents again.
 
124
        t.commit('first post')
 
125
        t.set_parent_ids([])
 
126
        self.assertConsistentParents([], t)
 
127
 
 
128
    def test_set_one_ghost_parent_ids_rejects(self):
 
129
        t = self.make_branch_and_tree('.')
 
130
        self.assertRaises(errors.GhostRevisionUnusableHere,
 
131
            t.set_parent_ids, ['missing-revision-id'])
 
132
 
 
133
    def test_set_one_ghost_parent_ids_force(self):
 
134
        t = self.make_branch_and_tree('.')
 
135
        t.set_parent_ids(['missing-revision-id'],
 
136
            allow_leftmost_as_ghost=True)
 
137
        self.assertConsistentParents(['missing-revision-id'], t)
 
138
 
 
139
    def test_set_two_parents_one_ghost_ids(self):
 
140
        t = self.make_branch_and_tree('.')
 
141
        revision_in_repo = t.commit('first post')
 
142
        # remove the tree's history
 
143
        uncommit(t.branch, tree=t)
 
144
        rev_tree = t.branch.repository.revision_tree(revision_in_repo)
 
145
        t.set_parent_ids([revision_in_repo, 'another-missing'])
 
146
        self.assertConsistentParents([revision_in_repo, 'another-missing'], t)
 
147
 
 
148
    def test_set_three_parents_ids(self):
 
149
        t = self.make_branch_and_tree('.')
 
150
        first_revision = t.commit('first post')
 
151
        uncommit(t.branch, tree=t)
 
152
        second_revision = t.commit('second post')
 
153
        uncommit(t.branch, tree=t)
 
154
        third_revision = t.commit('third post')
 
155
        uncommit(t.branch, tree=t)
 
156
        rev_tree1 = t.branch.repository.revision_tree(first_revision)
 
157
        rev_tree2 = t.branch.repository.revision_tree(second_revision)
 
158
        rev_tree3 = t.branch.repository.revision_tree(third_revision)
 
159
        t.set_parent_ids([first_revision, second_revision, third_revision])
 
160
        self.assertConsistentParents(
 
161
            [first_revision, second_revision, third_revision], t)
 
162
 
 
163
    def test_set_duplicate_parent_ids(self):
 
164
        t = self.make_branch_and_tree('.')
 
165
        rev1 = t.commit('first post')
 
166
        uncommit(t.branch, tree=t)
 
167
        rev2 = t.commit('second post')
 
168
        uncommit(t.branch, tree=t)
 
169
        rev3 = t.commit('third post')
 
170
        uncommit(t.branch, tree=t)
 
171
        t.set_parent_ids([rev1, rev2, rev2, rev3])
 
172
        # We strip the duplicate, but preserve the ordering
 
173
        self.assertConsistentParents([rev1, rev2, rev3], t)
 
174
 
 
175
    def test_set_duplicate_parent_trees(self):
 
176
        t = self.make_branch_and_tree('.')
 
177
        rev1 = t.commit('first post')
 
178
        uncommit(t.branch, tree=t)
 
179
        rev2 = t.commit('second post')
 
180
        uncommit(t.branch, tree=t)
 
181
        rev3 = t.commit('third post')
 
182
        uncommit(t.branch, tree=t)
 
183
        rev_tree1 = t.branch.repository.revision_tree(rev1)
 
184
        rev_tree2 = t.branch.repository.revision_tree(rev2)
 
185
        rev_tree3 = t.branch.repository.revision_tree(rev3)
 
186
        t.set_parent_trees([(rev1, rev_tree1), (rev2, rev_tree2),
 
187
                            (rev2, rev_tree2), (rev3, rev_tree3)])
 
188
        # We strip the duplicate, but preserve the ordering
 
189
        self.assertConsistentParents([rev1, rev2, rev3], t)
 
190
 
 
191
    def test_set_parent_ids_in_ancestry(self):
 
192
        t = self.make_branch_and_tree('.')
 
193
        rev1 = t.commit('first post')
 
194
        rev2 = t.commit('second post')
 
195
        rev3 = t.commit('third post')
 
196
        # Reset the tree, back to rev1
 
197
        t.set_parent_ids([rev1])
 
198
        t.branch.set_last_revision_info(1, rev1)
 
199
        self.assertConsistentParents([rev1], t)
 
200
        t.set_parent_ids([rev1, rev2, rev3])
 
201
        # rev2 is in the ancestry of rev3, so it will be filtered out
 
202
        self.assertConsistentParents([rev1, rev3], t)
 
203
        # Order should be preserved, and the first revision should always be
 
204
        # kept
 
205
        t.set_parent_ids([rev2, rev3, rev1])
 
206
        self.assertConsistentParents([rev2, rev3], t)
 
207
 
 
208
    def test_set_parent_trees_in_ancestry(self):
 
209
        t = self.make_branch_and_tree('.')
 
210
        rev1 = t.commit('first post')
 
211
        rev2 = t.commit('second post')
 
212
        rev3 = t.commit('third post')
 
213
        # Reset the tree, back to rev1
 
214
        t.set_parent_ids([rev1])
 
215
        t.branch.set_last_revision_info(1, rev1)
 
216
        self.assertConsistentParents([rev1], t)
 
217
        rev_tree1 = t.branch.repository.revision_tree(rev1)
 
218
        rev_tree2 = t.branch.repository.revision_tree(rev2)
 
219
        rev_tree3 = t.branch.repository.revision_tree(rev3)
 
220
        t.set_parent_trees([(rev1, rev_tree1), (rev2, rev_tree2),
 
221
                            (rev3, rev_tree3)])
 
222
        # rev2 is in the ancestry of rev3, so it will be filtered out
 
223
        self.assertConsistentParents([rev1, rev3], t)
 
224
        # Order should be preserved, and the first revision should always be
 
225
        # kept
 
226
        t.set_parent_trees([(rev2, rev_tree2), (rev1, rev_tree1),
 
227
                            (rev3, rev_tree3)])
 
228
        self.assertConsistentParents([rev2, rev3], t)
 
229
 
 
230
    def test_unicode_symlink(self):
 
231
        # this tests bug #272444
 
232
        self.requireFeature(tests.SymlinkFeature)
 
233
        self.requireFeature(tests.UnicodeFilenameFeature)
 
234
 
 
235
        tree = self.make_branch_and_tree('tree1')
 
236
 
 
237
        # The link points to a file whose name is an omega
 
238
        # U+03A9 GREEK CAPITAL LETTER OMEGA
 
239
        # UTF-8: ce a9  UTF-16BE: 03a9  Decimal: Ω
 
240
        target = u'\u03a9'
 
241
        link_name = u'\N{Euro Sign}link'
 
242
        os.symlink(target, 'tree1/' + link_name)
 
243
        tree.add([link_name],['link-id'])
 
244
 
 
245
        revision1 = tree.commit('added a link to a Unicode target')
 
246
        revision2 = tree.commit('this revision will be discarded')
 
247
        tree.set_parent_ids([revision1])
 
248
        tree.lock_read()
 
249
        self.addCleanup(tree.unlock)
 
250
        # Check that the symlink target is safely round-tripped in the trees.
 
251
        self.assertEqual(target, tree.get_symlink_target('link-id'))
 
252
        basis = tree.basis_tree()
 
253
        self.assertEqual(target, basis.get_symlink_target('link-id'))
 
254
 
 
255
 
 
256
class TestAddParent(TestParents):
 
257
 
 
258
    def test_add_first_parent_id(self):
 
259
        """Test adding the first parent id"""
 
260
        tree = self.make_branch_and_tree('.')
 
261
        first_revision = tree.commit('first post')
 
262
        uncommit(tree.branch, tree=tree)
 
263
        tree.add_parent_tree_id(first_revision)
 
264
        self.assertConsistentParents([first_revision], tree)
 
265
 
 
266
    def test_add_first_parent_id_ghost_rejects(self):
 
267
        """Test adding the first parent id - as a ghost"""
 
268
        tree = self.make_branch_and_tree('.')
 
269
        self.assertRaises(errors.GhostRevisionUnusableHere,
 
270
            tree.add_parent_tree_id, 'first-revision')
 
271
 
 
272
    def test_add_first_parent_id_ghost_force(self):
 
273
        """Test adding the first parent id - as a ghost"""
 
274
        tree = self.make_branch_and_tree('.')
 
275
        tree.add_parent_tree_id('first-revision', allow_leftmost_as_ghost=True)
 
276
        self.assertConsistentParents(['first-revision'], tree)
 
277
 
 
278
    def test_add_second_parent_id_with_ghost_first(self):
 
279
        """Test adding the second parent when the first is a ghost."""
 
280
        tree = self.make_branch_and_tree('.')
 
281
        tree.add_parent_tree_id('first-revision', allow_leftmost_as_ghost=True)
 
282
        tree.add_parent_tree_id('second')
 
283
        self.assertConsistentParents(['first-revision', 'second'], tree)
 
284
 
 
285
    def test_add_second_parent_id(self):
 
286
        """Test adding the second parent id"""
 
287
        tree = self.make_branch_and_tree('.')
 
288
        first_revision = tree.commit('first post')
 
289
        uncommit(tree.branch, tree=tree)
 
290
        second_revision = tree.commit('second post')
 
291
        tree.add_parent_tree_id(first_revision)
 
292
        self.assertConsistentParents([second_revision, first_revision], tree)
 
293
 
 
294
    def test_add_second_parent_id_ghost(self):
 
295
        """Test adding the second parent id - as a ghost"""
 
296
        tree = self.make_branch_and_tree('.')
 
297
        first_revision = tree.commit('first post')
 
298
        tree.add_parent_tree_id('second')
 
299
        self.assertConsistentParents([first_revision, 'second'], tree)
 
300
 
 
301
    def test_add_first_parent_tree(self):
 
302
        """Test adding the first parent id"""
 
303
        tree = self.make_branch_and_tree('.')
 
304
        first_revision = tree.commit('first post')
 
305
        uncommit(tree.branch, tree=tree)
 
306
        tree.add_parent_tree((first_revision,
 
307
            tree.branch.repository.revision_tree(first_revision)))
 
308
        self.assertConsistentParents([first_revision], tree)
 
309
 
 
310
    def test_add_first_parent_tree_ghost_rejects(self):
 
311
        """Test adding the first parent id - as a ghost"""
 
312
        tree = self.make_branch_and_tree('.')
 
313
        self.assertRaises(errors.GhostRevisionUnusableHere,
 
314
            tree.add_parent_tree, ('first-revision', None))
 
315
 
 
316
    def test_add_first_parent_tree_ghost_force(self):
 
317
        """Test adding the first parent id - as a ghost"""
 
318
        tree = self.make_branch_and_tree('.')
 
319
        tree.add_parent_tree(('first-revision', None),
 
320
            allow_leftmost_as_ghost=True)
 
321
        self.assertConsistentParents(['first-revision'], tree)
 
322
 
 
323
    def test_add_second_parent_tree(self):
 
324
        """Test adding the second parent id"""
 
325
        tree = self.make_branch_and_tree('.')
 
326
        first_revision = tree.commit('first post')
 
327
        uncommit(tree.branch, tree=tree)
 
328
        second_revision = tree.commit('second post')
 
329
        tree.add_parent_tree((first_revision,
 
330
            tree.branch.repository.revision_tree(first_revision)))
 
331
        self.assertConsistentParents([second_revision, first_revision], tree)
 
332
 
 
333
    def test_add_second_parent_tree_ghost(self):
 
334
        """Test adding the second parent id - as a ghost"""
 
335
        tree = self.make_branch_and_tree('.')
 
336
        first_revision = tree.commit('first post')
 
337
        tree.add_parent_tree(('second', None))
 
338
        self.assertConsistentParents([first_revision, 'second'], tree)
 
339
 
 
340
 
 
341
class UpdateToOneParentViaDeltaTests(TestCaseWithWorkingTree):
 
342
    """Tests for the update_basis_by_delta call.
 
343
 
 
344
    This is intuitively defined as 'apply an inventory delta to the basis and
 
345
    discard other parents', but for trees that have an inventory that is not
 
346
    managed as a tree-by-id, the implementation requires roughly duplicated
 
347
    tests with those for apply_inventory_delta on the main tree.
 
348
    """
 
349
 
 
350
    def assertDeltaApplicationResultsInExpectedBasis(self, tree, revid, delta,
 
351
        expected_inventory):
 
352
        tree.lock_write()
 
353
        try:
 
354
            tree.update_basis_by_delta(revid, delta)
 
355
        finally:
 
356
            tree.unlock()
 
357
        # check the last revision was adjusted to rev_id
 
358
        self.assertEqual(revid, tree.last_revision())
 
359
        # check the parents are what we expect
 
360
        self.assertEqual([revid], tree.get_parent_ids())
 
361
        # check that the basis tree has the inventory we expect from applying
 
362
        # the delta.
 
363
        result_basis = tree.basis_tree()
 
364
        result_basis.lock_read()
 
365
        try:
 
366
            self.assertEqual(expected_inventory, result_basis.inventory)
 
367
        finally:
 
368
            result_basis.unlock()
 
369
 
 
370
    def make_inv_delta(self, old, new):
 
371
        """Make an inventory delta from two inventories."""
 
372
        old_ids = set(old._byid.iterkeys())
 
373
        new_ids = set(new._byid.iterkeys())
 
374
        adds = new_ids - old_ids
 
375
        deletes = old_ids - new_ids
 
376
        common = old_ids.intersection(new_ids)
 
377
        delta = []
 
378
        for file_id in deletes:
 
379
            delta.append((old.id2path(file_id), None, file_id, None))
 
380
        for file_id in adds:
 
381
            delta.append((None, new.id2path(file_id), file_id, new[file_id]))
 
382
        for file_id in common:
 
383
            if old[file_id] != new[file_id]:
 
384
                delta.append((old.id2path(file_id), new.id2path(file_id),
 
385
                    file_id, new[file_id]))
 
386
        return delta
 
387
 
 
388
    def fake_up_revision(self, tree, revid, shape):
 
389
        tree.lock_write()
 
390
        try:
 
391
            tree.branch.repository.start_write_group()
 
392
            try:
 
393
                if shape.root.revision is None:
 
394
                    shape.root.revision = revid
 
395
                # Create the text records for this inventory.
 
396
                for path, ie in shape.iter_entries():
 
397
                    if ie.text_size:
 
398
                        lines = ['a' * ie.text_size]
 
399
                    else:
 
400
                        lines = []
 
401
                    tree.branch.repository.texts.add_lines(
 
402
                        (ie.file_id, ie.revision), [], lines)
 
403
                sha1 = tree.branch.repository.add_inventory(revid, shape, [])
 
404
                rev = Revision(timestamp=0,
 
405
                               timezone=None,
 
406
                               committer="Foo Bar <foo@example.com>",
 
407
                               message="Message",
 
408
                               inventory_sha1=sha1,
 
409
                               revision_id=revid)
 
410
                tree.branch.repository.add_revision(revid, rev)
 
411
                tree.branch.repository.commit_write_group()
 
412
            except:
 
413
                tree.branch.repository.abort_write_group()
 
414
                raise
 
415
        finally:
 
416
            tree.unlock()
 
417
 
 
418
    def add_entry(self, inv, rev_id, entry):
 
419
        entry.revision = rev_id
 
420
        inv.add(entry)
 
421
 
 
422
    def add_dir(self, inv, rev_id, file_id, parent_id, name):
 
423
        new_dir = InventoryDirectory(file_id, name, parent_id)
 
424
        self.add_entry(inv, rev_id, new_dir)
 
425
 
 
426
    def add_file(self, inv, rev_id, file_id, parent_id, name, sha, size):
 
427
        new_file = InventoryFile(file_id, name, parent_id)
 
428
        new_file.text_sha1 = sha
 
429
        new_file.text_size = size
 
430
        self.add_entry(inv, rev_id, new_file)
 
431
 
 
432
    def add_link(self, inv, rev_id, file_id, parent_id, name, target):
 
433
        new_link = InventoryLink(file_id, name, parent_id)
 
434
        new_link.symlink_target = target
 
435
        self.add_entry(inv, rev_id, new_link)
 
436
 
 
437
    def add_new_root(self, new_shape, old_revid, new_revid):
 
438
        if self.bzrdir_format.repository_format.rich_root_data:
 
439
            self.add_dir(new_shape, old_revid, 'root-id', None, '')
 
440
        else:
 
441
            self.add_dir(new_shape, new_revid, 'root-id', None, '')
 
442
 
 
443
    def assertTransitionFromBasisToShape(self, basis_shape, basis_revid,
 
444
        new_shape, new_revid, extra_parent=None):
 
445
        # set the inventory revision ids.
 
446
        basis_shape.revision_id = basis_revid
 
447
        new_shape.revision_id = new_revid
 
448
        delta = self.make_inv_delta(basis_shape, new_shape)
 
449
        tree = self.make_branch_and_tree('tree')
 
450
        # the shapes need to be in the tree's repository to be able to set them
 
451
        # as a parent, but the file content is not needed.
 
452
        if basis_revid is not None:
 
453
            self.fake_up_revision(tree, basis_revid, basis_shape)
 
454
            parents = [basis_revid]
 
455
            if extra_parent is not None:
 
456
                parents.append(extra_parent)
 
457
            tree.set_parent_ids(parents)
 
458
        self.fake_up_revision(tree, new_revid, new_shape)
 
459
        # give tree an inventory of new_shape
 
460
        tree._write_inventory(new_shape)
 
461
        self.assertDeltaApplicationResultsInExpectedBasis(tree, new_revid,
 
462
            delta, new_shape)
 
463
        # The tree should be internally consistent; while this is a moderately
 
464
        # large hammer, this is a particularly sensitive area of code, so the
 
465
        # extra assurance is well worth it.
 
466
        tree._validate()
 
467
        osutils.rmtree('tree')
 
468
 
 
469
    def test_no_parents_just_root(self):
 
470
        """Test doing an empty commit - no parent, set a root only."""
 
471
        basis_shape = Inventory(root_id=None) # empty tree
 
472
        new_shape = Inventory() # tree with a root
 
473
        self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
 
474
            'new_parent')
 
475
 
 
476
    def test_no_parents_full_tree(self):
 
477
        """Test doing a regular initial commit with files and dirs."""
 
478
        basis_shape = Inventory(root_id=None) # empty tree
 
479
        revid = 'new-parent'
 
480
        new_shape = Inventory(root_id=None)
 
481
        self.add_dir(new_shape, revid, 'root-id', None, '')
 
482
        self.add_link(new_shape, revid, 'link-id', 'root-id', 'link', 'target')
 
483
        self.add_file(new_shape, revid, 'file-id', 'root-id', 'file', '1' * 32,
 
484
            12)
 
485
        self.add_dir(new_shape, revid, 'dir-id', 'root-id', 'dir')
 
486
        self.add_file(new_shape, revid, 'subfile-id', 'dir-id', 'subfile',
 
487
            '2' * 32, 24)
 
488
        self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
 
489
            revid)
 
490
 
 
491
    def test_file_content_change(self):
 
492
        old_revid = 'old-parent'
 
493
        basis_shape = Inventory(root_id=None)
 
494
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
495
        self.add_file(basis_shape, old_revid, 'file-id', 'root-id', 'file',
 
496
            '1' * 32, 12)
 
497
        new_revid = 'new-parent'
 
498
        new_shape = Inventory(root_id=None)
 
499
        self.add_new_root(new_shape, old_revid, new_revid)
 
500
        self.add_file(new_shape, new_revid, 'file-id', 'root-id', 'file',
 
501
            '2' * 32, 24)
 
502
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
503
            new_shape, new_revid)
 
504
 
 
505
    def test_link_content_change(self):
 
506
        old_revid = 'old-parent'
 
507
        basis_shape = Inventory(root_id=None)
 
508
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
509
        self.add_link(basis_shape, old_revid, 'link-id', 'root-id', 'link',
 
510
            'old-target')
 
511
        new_revid = 'new-parent'
 
512
        new_shape = Inventory(root_id=None)
 
513
        self.add_new_root(new_shape, old_revid, new_revid)
 
514
        self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
 
515
            'new-target')
 
516
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
517
            new_shape, new_revid)
 
518
 
 
519
    def test_kind_changes(self):
 
520
        def do_file(inv, revid):
 
521
            self.add_file(inv, revid, 'path-id', 'root-id', 'path', '1' * 32,
 
522
                12)
 
523
        def do_link(inv, revid):
 
524
            self.add_link(inv, revid, 'path-id', 'root-id', 'path', 'target')
 
525
        def do_dir(inv, revid):
 
526
            self.add_dir(inv, revid, 'path-id', 'root-id', 'path')
 
527
        for old_factory in (do_file, do_link, do_dir):
 
528
            for new_factory in (do_file, do_link, do_dir):
 
529
                if old_factory == new_factory:
 
530
                    continue
 
531
                old_revid = 'old-parent'
 
532
                basis_shape = Inventory(root_id=None)
 
533
                self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
534
                old_factory(basis_shape, old_revid)
 
535
                new_revid = 'new-parent'
 
536
                new_shape = Inventory(root_id=None)
 
537
                self.add_new_root(new_shape, old_revid, new_revid)
 
538
                new_factory(new_shape, new_revid)
 
539
                self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
540
                    new_shape, new_revid)
 
541
 
 
542
    def test_content_from_second_parent_is_dropped(self):
 
543
        left_revid = 'left-parent'
 
544
        basis_shape = Inventory(root_id=None)
 
545
        self.add_dir(basis_shape, left_revid, 'root-id', None, '')
 
546
        self.add_link(basis_shape, left_revid, 'link-id', 'root-id', 'link',
 
547
            'left-target')
 
548
        # the right shape has content - file, link, subdir with a child,
 
549
        # that should all be discarded by the call.
 
550
        right_revid = 'right-parent'
 
551
        right_shape = Inventory(root_id=None)
 
552
        self.add_dir(right_shape, left_revid, 'root-id', None, '')
 
553
        self.add_link(right_shape, right_revid, 'link-id', 'root-id', 'link',
 
554
            'some-target')
 
555
        self.add_dir(right_shape, right_revid, 'subdir-id', 'root-id', 'dir')
 
556
        self.add_file(right_shape, right_revid, 'file-id', 'subdir-id', 'file',
 
557
            '2' * 32, 24)
 
558
        new_revid = 'new-parent'
 
559
        new_shape = Inventory(root_id=None)
 
560
        self.add_new_root(new_shape, left_revid, new_revid)
 
561
        self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
 
562
            'new-target')
 
563
        self.assertTransitionFromBasisToShape(basis_shape, left_revid,
 
564
            new_shape, new_revid, right_revid)
 
565
 
 
566
    def test_parent_id_changed(self):
 
567
        # test that when the only change to an entry is its parent id changing
 
568
        # that it is handled correctly (that is it keeps the same path)
 
569
        old_revid = 'old-parent'
 
570
        basis_shape = Inventory(root_id=None)
 
571
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
572
        self.add_dir(basis_shape, old_revid, 'orig-parent-id', 'root-id', 'dir')
 
573
        self.add_dir(basis_shape, old_revid, 'dir-id', 'orig-parent-id', 'dir')
 
574
        new_revid = 'new-parent'
 
575
        new_shape = Inventory(root_id=None)
 
576
        self.add_new_root(new_shape, old_revid, new_revid)
 
577
        self.add_dir(new_shape, new_revid, 'new-parent-id', 'root-id', 'dir')
 
578
        self.add_dir(new_shape, new_revid, 'dir-id', 'new-parent-id', 'dir')
 
579
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
580
            new_shape, new_revid)
 
581
 
 
582
    def test_name_changed(self):
 
583
        # test that when the only change to an entry is its name changing that
 
584
        # it is handled correctly (that is it keeps the same parent id)
 
585
        old_revid = 'old-parent'
 
586
        basis_shape = Inventory(root_id=None)
 
587
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
588
        self.add_dir(basis_shape, old_revid, 'parent-id', 'root-id', 'origdir')
 
589
        self.add_dir(basis_shape, old_revid, 'dir-id', 'parent-id', 'olddir')
 
590
        new_revid = 'new-parent'
 
591
        new_shape = Inventory(root_id=None)
 
592
        self.add_new_root(new_shape, old_revid, new_revid)
 
593
        self.add_dir(new_shape, new_revid, 'parent-id', 'root-id', 'newdir')
 
594
        self.add_dir(new_shape, new_revid, 'dir-id', 'parent-id', 'newdir')
 
595
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
596
            new_shape, new_revid)
 
597
 
 
598
    def test_parent_child_swap(self):
 
599
        # test a A->A/B and A/B->A path swap.
 
600
        old_revid = 'old-parent'
 
601
        basis_shape = Inventory(root_id=None)
 
602
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
603
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
 
604
        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
 
605
        self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
 
606
        new_revid = 'new-parent'
 
607
        new_shape = Inventory(root_id=None)
 
608
        self.add_new_root(new_shape, old_revid, new_revid)
 
609
        self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
 
610
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'dir-id-B', 'B')
 
611
        self.add_link(new_shape, new_revid, 'link-id-C', 'dir-id-A', 'C', 'C')
 
612
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
613
            new_shape, new_revid)
 
614
 
 
615
    def test_parent_deleted_child_renamed(self):
 
616
        # test a A->None and A/B->A.
 
617
        old_revid = 'old-parent'
 
618
        basis_shape = Inventory(root_id=None)
 
619
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
620
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
 
621
        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
 
622
        self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
 
623
        new_revid = 'new-parent'
 
624
        new_shape = Inventory(root_id=None)
 
625
        self.add_new_root(new_shape, old_revid, new_revid)
 
626
        self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
 
627
        self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
 
628
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
629
            new_shape, new_revid)
 
630
 
 
631
    def test_dir_to_root(self):
 
632
        # test a A->''.
 
633
        old_revid = 'old-parent'
 
634
        basis_shape = Inventory(root_id=None)
 
635
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
636
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
 
637
        self.add_link(basis_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'B')
 
638
        new_revid = 'new-parent'
 
639
        new_shape = Inventory(root_id=None)
 
640
        self.add_dir(new_shape, new_revid, 'dir-id-A', None, '')
 
641
        self.add_link(new_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'B')
 
642
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
643
            new_shape, new_revid)
 
644
 
 
645
    def test_path_swap(self):
 
646
        # test a A->B and B->A path swap.
 
647
        old_revid = 'old-parent'
 
648
        basis_shape = Inventory(root_id=None)
 
649
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
650
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
 
651
        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'root-id', 'B')
 
652
        self.add_link(basis_shape, old_revid, 'link-id-C', 'root-id', 'C', 'C')
 
653
        self.add_link(basis_shape, old_revid, 'link-id-D', 'root-id', 'D', 'D')
 
654
        self.add_file(basis_shape, old_revid, 'file-id-E', 'root-id', 'E',
 
655
            '1' * 32, 12)
 
656
        self.add_file(basis_shape, old_revid, 'file-id-F', 'root-id', 'F',
 
657
            '2' * 32, 24)
 
658
        new_revid = 'new-parent'
 
659
        new_shape = Inventory(root_id=None)
 
660
        self.add_new_root(new_shape, old_revid, new_revid)
 
661
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'B')
 
662
        self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
 
663
        self.add_link(new_shape, new_revid, 'link-id-C', 'root-id', 'D', 'C')
 
664
        self.add_link(new_shape, new_revid, 'link-id-D', 'root-id', 'C', 'D')
 
665
        self.add_file(new_shape, new_revid, 'file-id-E', 'root-id', 'F',
 
666
            '1' * 32, 12)
 
667
        self.add_file(new_shape, new_revid, 'file-id-F', 'root-id', 'E',
 
668
            '2' * 32, 24)
 
669
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
670
            new_shape, new_revid)
 
671
 
 
672
    def test_adds(self):
 
673
        # test adding paths and dirs, including adding to a newly added dir.
 
674
        old_revid = 'old-parent'
 
675
        basis_shape = Inventory(root_id=None)
 
676
        # with a root, so its a commit after the first.
 
677
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
678
        new_revid = 'new-parent'
 
679
        new_shape = Inventory(root_id=None)
 
680
        self.add_new_root(new_shape, old_revid, new_revid)
 
681
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
 
682
        self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
 
683
        self.add_file(new_shape, new_revid, 'file-id-C', 'root-id', 'C',
 
684
            '1' * 32, 12)
 
685
        self.add_file(new_shape, new_revid, 'file-id-D', 'dir-id-A', 'D',
 
686
            '2' * 32, 24)
 
687
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
688
            new_shape, new_revid)
 
689
 
 
690
    def test_removes(self):
 
691
        # test removing paths, including paths that are within other also
 
692
        # removed paths.
 
693
        old_revid = 'old-parent'
 
694
        basis_shape = Inventory(root_id=None)
 
695
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
696
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
 
697
        self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
 
698
        self.add_file(basis_shape, old_revid, 'file-id-C', 'root-id', 'C',
 
699
            '1' * 32, 12)
 
700
        self.add_file(basis_shape, old_revid, 'file-id-D', 'dir-id-A', 'D',
 
701
            '2' * 32, 24)
 
702
        new_revid = 'new-parent'
 
703
        new_shape = Inventory(root_id=None)
 
704
        self.add_new_root(new_shape, old_revid, new_revid)
 
705
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
706
            new_shape, new_revid)
 
707
 
 
708
    def test_move_to_added_dir(self):
 
709
        old_revid = 'old-parent'
 
710
        basis_shape = Inventory(root_id=None)
 
711
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
712
        self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
 
713
        new_revid = 'new-parent'
 
714
        new_shape = Inventory(root_id=None)
 
715
        self.add_new_root(new_shape, old_revid, new_revid)
 
716
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
 
717
        self.add_link(new_shape, new_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
 
718
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
719
            new_shape, new_revid)
 
720
 
 
721
    def test_move_from_removed_dir(self):
 
722
        old_revid = 'old-parent'
 
723
        basis_shape = Inventory(root_id=None)
 
724
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
725
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
 
726
        self.add_link(basis_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
 
727
        new_revid = 'new-parent'
 
728
        new_shape = Inventory(root_id=None)
 
729
        self.add_new_root(new_shape, old_revid, new_revid)
 
730
        self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
 
731
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
732
            new_shape, new_revid)
 
733
 
 
734
    def test_move_moves_children_recursively(self):
 
735
        old_revid = 'old-parent'
 
736
        basis_shape = Inventory(root_id=None)
 
737
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
 
738
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
 
739
        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
 
740
        self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
 
741
        new_revid = 'new-parent'
 
742
        new_shape = Inventory(root_id=None)
 
743
        self.add_new_root(new_shape, old_revid, new_revid)
 
744
        # the moved path:
 
745
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'B')
 
746
        # unmoved children.
 
747
        self.add_dir(new_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
 
748
        self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
 
749
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
 
750
            new_shape, new_revid)