~bzr-pqm/bzr/bzr.dev

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