~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
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
19
from errno import EEXIST
1908.5.2 by Robert Collins
Create and test set_parent_trees.
20
import os
21
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
22
from bzrlib import (
23
    errors,
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
24
    osutils,
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
25
    revision as _mod_revision,
26
    symbol_versioning,
4095.3.4 by Vincent Ladeuil
Some trivial cleanups, related to fix for bug #272444.
27
    tests,
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
28
    )
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
29
from bzrlib.inventory import (
30
    Inventory,
31
    InventoryFile,
32
    InventoryDirectory,
33
    InventoryLink,
34
    )
35
from bzrlib.revision import Revision
1908.5.2 by Robert Collins
Create and test set_parent_trees.
36
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
37
from bzrlib.uncommit import uncommit
38
39
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.
40
class TestParents(TestCaseWithWorkingTree):
41
42
    def assertConsistentParents(self, expected, tree):
1908.7.6 by Robert Collins
Deprecate WorkingTree.last_revision.
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
        """
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.
48
        self.assertEqual(expected, tree.get_parent_ids())
49
        if expected == []:
2598.5.7 by Aaron Bentley
Updates from review
50
            self.assertEqual(_mod_revision.NULL_REVISION,
51
                             _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.
52
        else:
1908.7.11 by Robert Collins
Merge bzr.dev and undeprecated WorkingTree.last_revision as per review feedback.
53
            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.
54
55
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
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
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.
63
class TestSetParents(TestParents):
1908.5.2 by Robert Collins
Create and test set_parent_trees.
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([])
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.
72
        self.assertConsistentParents([], t)
1908.5.2 by Robert Collins
Create and test set_parent_trees.
73
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
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
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.
81
    def test_set_one_ghost_parent_rejects(self):
82
        t = self.make_branch_and_tree('.')
1908.5.12 by Robert Collins
Apply review feedback - paired with Martin.
83
        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.
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)
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.
90
        self.assertConsistentParents(['missing-revision-id'], t)
1908.5.2 by Robert Collins
Create and test set_parent_trees.
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)])
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.
100
        self.assertConsistentParents([revision_in_repo, 'another-missing'], t)
1908.5.2 by Robert Collins
Create and test set_parent_trees.
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)])
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.
116
        self.assertConsistentParents(
117
            [first_revision, second_revision, third_revision], t)
1908.5.4 by Robert Collins
Add add_parent_tree_id WorkingTree helper api.
118
1908.5.5 by Robert Collins
Add WorkingTree.set_parent_ids.
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
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.
128
    def test_set_one_ghost_parent_ids_rejects(self):
129
        t = self.make_branch_and_tree('.')
1908.5.12 by Robert Collins
Apply review feedback - paired with Martin.
130
        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.
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)
1908.5.5 by Robert Collins
Add WorkingTree.set_parent_ids.
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
3462.1.2 by John Arbash Meinel
Change WT.set_parent_(ids/trees) to filter out ancestors.
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
3763.9.7 by Daniel Clemente
Tested Unicode target rather than always trying to create it in UTF-8. Test renamed
230
    def test_unicode_symlink(self):
3763.9.1 by Daniel Clemente
New testcase for bug 272444 (support symlinks to non-ASCII files)
231
        # this tests bug #272444
4095.3.4 by Vincent Ladeuil
Some trivial cleanups, related to fix for bug #272444.
232
        self.requireFeature(tests.SymlinkFeature)
233
        self.requireFeature(tests.UnicodeFilenameFeature)
3763.9.1 by Daniel Clemente
New testcase for bug 272444 (support symlinks to non-ASCII files)
234
3763.9.3 by Daniel Clemente
Clearer test with better names and just one parent
235
        tree = self.make_branch_and_tree('tree1')
3763.9.2 by Daniel Clemente
Adapted test to use set_parent_ids and raise KnownFailure
236
3763.9.9 by Daniel Clemente
Used a greek omega instead of an accented 'o' to avoid combining characters
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
        os.symlink(u'\u03a9','tree1/link_name')
3763.9.3 by Daniel Clemente
Clearer test with better names and just one parent
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])
3763.9.1 by Daniel Clemente
New testcase for bug 272444 (support symlinks to non-ASCII files)
246
1908.5.4 by Robert Collins
Add add_parent_tree_id WorkingTree helper api.
247
1908.5.6 by Robert Collins
Add add_parent_tree to WorkingTree.
248
class TestAddParent(TestParents):
1908.5.4 by Robert Collins
Add add_parent_tree_id WorkingTree helper api.
249
250
    def test_add_first_parent_id(self):
251
        """Test adding the first parent id"""
252
        tree = self.make_branch_and_tree('.')
253
        first_revision = tree.commit('first post')
254
        uncommit(tree.branch, tree=tree)
255
        tree.add_parent_tree_id(first_revision)
256
        self.assertConsistentParents([first_revision], tree)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
257
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.
258
    def test_add_first_parent_id_ghost_rejects(self):
259
        """Test adding the first parent id - as a ghost"""
260
        tree = self.make_branch_and_tree('.')
1908.5.12 by Robert Collins
Apply review feedback - paired with Martin.
261
        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.
262
            tree.add_parent_tree_id, 'first-revision')
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_force(self):
265
        """Test adding the first parent id - as a ghost"""
266
        tree = self.make_branch_and_tree('.')
267
        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.
268
        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.
269
270
    def test_add_second_parent_id_with_ghost_first(self):
271
        """Test adding the second parent when the first is a ghost."""
272
        tree = self.make_branch_and_tree('.')
273
        tree.add_parent_tree_id('first-revision', allow_leftmost_as_ghost=True)
274
        tree.add_parent_tree_id('second')
275
        self.assertConsistentParents(['first-revision', 'second'], tree)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
276
1908.5.4 by Robert Collins
Add add_parent_tree_id WorkingTree helper api.
277
    def test_add_second_parent_id(self):
278
        """Test adding the second parent id"""
279
        tree = self.make_branch_and_tree('.')
280
        first_revision = tree.commit('first post')
281
        uncommit(tree.branch, tree=tree)
282
        second_revision = tree.commit('second post')
283
        tree.add_parent_tree_id(first_revision)
284
        self.assertConsistentParents([second_revision, first_revision], tree)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
285
1908.5.4 by Robert Collins
Add add_parent_tree_id WorkingTree helper api.
286
    def test_add_second_parent_id_ghost(self):
287
        """Test adding the second parent id - as a ghost"""
288
        tree = self.make_branch_and_tree('.')
289
        first_revision = tree.commit('first post')
290
        tree.add_parent_tree_id('second')
291
        self.assertConsistentParents([first_revision, 'second'], tree)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
292
1908.5.6 by Robert Collins
Add add_parent_tree to WorkingTree.
293
    def test_add_first_parent_tree(self):
294
        """Test adding the first parent id"""
295
        tree = self.make_branch_and_tree('.')
296
        first_revision = tree.commit('first post')
297
        uncommit(tree.branch, tree=tree)
298
        tree.add_parent_tree((first_revision,
299
            tree.branch.repository.revision_tree(first_revision)))
300
        self.assertConsistentParents([first_revision], tree)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
301
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.
302
    def test_add_first_parent_tree_ghost_rejects(self):
303
        """Test adding the first parent id - as a ghost"""
304
        tree = self.make_branch_and_tree('.')
1908.5.12 by Robert Collins
Apply review feedback - paired with Martin.
305
        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.
306
            tree.add_parent_tree, ('first-revision', None))
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_force(self):
309
        """Test adding the first parent id - as a ghost"""
310
        tree = self.make_branch_and_tree('.')
311
        tree.add_parent_tree(('first-revision', None),
312
            allow_leftmost_as_ghost=True)
1908.5.6 by Robert Collins
Add add_parent_tree to WorkingTree.
313
        self.assertConsistentParents(['first-revision'], tree)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
314
1908.5.6 by Robert Collins
Add add_parent_tree to WorkingTree.
315
    def test_add_second_parent_tree(self):
316
        """Test adding the second parent id"""
317
        tree = self.make_branch_and_tree('.')
318
        first_revision = tree.commit('first post')
319
        uncommit(tree.branch, tree=tree)
320
        second_revision = tree.commit('second post')
321
        tree.add_parent_tree((first_revision,
322
            tree.branch.repository.revision_tree(first_revision)))
323
        self.assertConsistentParents([second_revision, first_revision], tree)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
324
1908.5.6 by Robert Collins
Add add_parent_tree to WorkingTree.
325
    def test_add_second_parent_tree_ghost(self):
326
        """Test adding the second parent id - as a ghost"""
327
        tree = self.make_branch_and_tree('.')
328
        first_revision = tree.commit('first post')
329
        tree.add_parent_tree(('second', None))
330
        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.
331
332
3253.2.1 by John Arbash Meinel
Fix moving directories to root nodes.
333
class UpdateToOneParentViaDeltaTests(TestCaseWithWorkingTree):
2903.2.7 by Martin Pool
Rename update_to_one_parent_via_delta to more wieldy update_basis_by_delta
334
    """Tests for the update_basis_by_delta call.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
335
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
336
    This is intuitively defined as 'apply an inventory delta to the basis and
337
    discard other parents', but for trees that have an inventory that is not
338
    managed as a tree-by-id, the implementation requires roughly duplicated
339
    tests with those for apply_inventory_delta on the main tree.
340
    """
341
342
    def assertDeltaApplicationResultsInExpectedBasis(self, tree, revid, delta,
343
        expected_inventory):
2929.2.1 by Robert Collins
* Commit updates the state of the working tree via a delta rather than
344
        tree.lock_write()
345
        try:
346
            tree.update_basis_by_delta(revid, delta)
347
        finally:
348
            tree.unlock()
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
349
        # check the last revision was adjusted to rev_id
350
        self.assertEqual(revid, tree.last_revision())
351
        # check the parents are what we expect
352
        self.assertEqual([revid], tree.get_parent_ids())
353
        # check that the basis tree has the inventory we expect from applying
354
        # the delta.
355
        result_basis = tree.basis_tree()
356
        result_basis.lock_read()
3619.6.6 by Mark Hammond
eagerly unlock the result_basis to prevent handles staying open.
357
        try:
358
            self.assertEqual(expected_inventory, result_basis.inventory)
359
        finally:
360
            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.
361
362
    def make_inv_delta(self, old, new):
363
        """Make an inventory delta from two inventories."""
364
        old_ids = set(old._byid.iterkeys())
365
        new_ids = set(new._byid.iterkeys())
366
        adds = new_ids - old_ids
367
        deletes = old_ids - new_ids
368
        common = old_ids.intersection(new_ids)
369
        delta = []
370
        for file_id in deletes:
371
            delta.append((old.id2path(file_id), None, file_id, None))
372
        for file_id in adds:
373
            delta.append((None, new.id2path(file_id), file_id, new[file_id]))
374
        for file_id in common:
375
            if old[file_id] != new[file_id]:
376
                delta.append((old.id2path(file_id), new.id2path(file_id),
377
                    file_id, new[file_id]))
378
        return delta
379
380
    def fake_up_revision(self, tree, revid, shape):
381
        tree.lock_write()
382
        try:
383
            tree.branch.repository.start_write_group()
384
            try:
385
                if shape.root.revision is None:
386
                    shape.root.revision = revid
387
                sha1 = tree.branch.repository.add_inventory(revid, shape, [])
388
                rev = Revision(timestamp=0,
389
                               timezone=None,
390
                               committer="Foo Bar <foo@example.com>",
391
                               message="Message",
392
                               inventory_sha1=sha1,
393
                               revision_id=revid)
394
                tree.branch.repository.add_revision(revid, rev)
395
            except:
396
                tree.branch.repository.abort_write_group()
397
                raise
398
            else:
399
                tree.branch.repository.commit_write_group()
400
        finally:
401
            tree.unlock()
402
403
    def add_entry(self, inv, rev_id, entry):
404
        entry.revision = rev_id
405
        inv.add(entry)
406
407
    def add_dir(self, inv, rev_id, file_id, parent_id, name):
408
        new_dir = InventoryDirectory(file_id, name, parent_id)
409
        self.add_entry(inv, rev_id, new_dir)
410
411
    def add_file(self, inv, rev_id, file_id, parent_id, name, sha, size):
412
        new_file = InventoryFile(file_id, name, parent_id)
413
        new_file.text_sha1 = sha
414
        new_file.text_size = size
415
        self.add_entry(inv, rev_id, new_file)
416
417
    def add_link(self, inv, rev_id, file_id, parent_id, name, target):
418
        new_link = InventoryLink(file_id, name, parent_id)
419
        new_link.symlink_target = target
420
        self.add_entry(inv, rev_id, new_link)
421
422
    def add_new_root(self, new_shape, old_revid, new_revid):
423
        if self.bzrdir_format.repository_format.rich_root_data:
424
            self.add_dir(new_shape, old_revid, 'root-id', None, '')
425
        else:
426
            self.add_dir(new_shape, new_revid, 'root-id', None, '')
427
428
    def assertTransitionFromBasisToShape(self, basis_shape, basis_revid,
429
        new_shape, new_revid, extra_parent=None):
2889.1.1 by Robert Collins
* The class ``bzrlib.repofmt.knitrepo.KnitRepository3`` has been folded into
430
        # set the inventory revision ids.
431
        basis_shape.revision_id = basis_revid
432
        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.
433
        delta = self.make_inv_delta(basis_shape, new_shape)
434
        tree = self.make_branch_and_tree('tree')
435
        # the shapes need to be in the tree's repository to be able to set them
436
        # as a parent, but the file content is not needed.
437
        if basis_revid is not None:
438
            self.fake_up_revision(tree, basis_revid, basis_shape)
439
            parents = [basis_revid]
440
            if extra_parent is not None:
441
                parents.append(extra_parent)
442
            tree.set_parent_ids(parents)
443
        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
444
        # give tree an inventory of new_shape
445
        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.
446
        self.assertDeltaApplicationResultsInExpectedBasis(tree, new_revid,
447
            delta, new_shape)
2929.2.2 by Robert Collins
Review feedback on dirstate update_basis_via_delta logic.
448
        # The tree should be internally consistent; while this is a moderately
449
        # large hammer, this is a particularly sensitive area of code, so the
450
        # extra assurance is well worth it.
451
        tree._validate()
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
452
        osutils.rmtree('tree')
453
454
    def test_no_parents_just_root(self):
455
        """Test doing an empty commit - no parent, set a root only."""
456
        basis_shape = Inventory(root_id=None) # empty tree
457
        new_shape = Inventory() # tree with a root
458
        self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
459
            'new_parent')
460
461
    def test_no_parents_full_tree(self):
462
        """Test doing a regular initial commit with files and dirs."""
463
        basis_shape = Inventory(root_id=None) # empty tree
464
        revid = 'new-parent'
465
        new_shape = Inventory(root_id=None)
466
        self.add_dir(new_shape, revid, 'root-id', None, '')
467
        self.add_link(new_shape, revid, 'link-id', 'root-id', 'link', 'target')
468
        self.add_file(new_shape, revid, 'file-id', 'root-id', 'file', '1' * 32,
469
            12)
470
        self.add_dir(new_shape, revid, 'dir-id', 'root-id', 'dir')
471
        self.add_file(new_shape, revid, 'subfile-id', 'dir-id', 'subfile',
472
            '2' * 32, 24)
473
        self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
474
            revid)
475
476
    def test_file_content_change(self):
477
        old_revid = 'old-parent'
478
        basis_shape = Inventory(root_id=None)
479
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
480
        self.add_file(basis_shape, old_revid, 'file-id', 'root-id', 'file',
481
            '1' * 32, 12)
482
        new_revid = 'new-parent'
483
        new_shape = Inventory(root_id=None)
484
        self.add_new_root(new_shape, old_revid, new_revid)
485
        self.add_file(new_shape, new_revid, 'file-id', 'root-id', 'file',
486
            '2' * 32, 24)
487
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
488
            new_shape, new_revid)
489
490
    def test_link_content_change(self):
491
        old_revid = 'old-parent'
492
        basis_shape = Inventory(root_id=None)
493
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
494
        self.add_link(basis_shape, old_revid, 'link-id', 'root-id', 'link',
495
            'old-target')
496
        new_revid = 'new-parent'
497
        new_shape = Inventory(root_id=None)
498
        self.add_new_root(new_shape, old_revid, new_revid)
499
        self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
500
            'new-target')
501
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
502
            new_shape, new_revid)
503
504
    def test_kind_changes(self):
505
        def do_file(inv, revid):
506
            self.add_file(inv, revid, 'path-id', 'root-id', 'path', '1' * 32,
507
                12)
508
        def do_link(inv, revid):
509
            self.add_link(inv, revid, 'path-id', 'root-id', 'path', 'target')
510
        def do_dir(inv, revid):
511
            self.add_dir(inv, revid, 'path-id', 'root-id', 'path')
512
        for old_factory in (do_file, do_link, do_dir):
513
            for new_factory in (do_file, do_link, do_dir):
514
                if old_factory == new_factory:
515
                    continue
516
                old_revid = 'old-parent'
517
                basis_shape = Inventory(root_id=None)
518
                self.add_dir(basis_shape, old_revid, 'root-id', None, '')
519
                old_factory(basis_shape, old_revid)
520
                new_revid = 'new-parent'
521
                new_shape = Inventory(root_id=None)
522
                self.add_new_root(new_shape, old_revid, new_revid)
523
                new_factory(new_shape, new_revid)
524
                self.assertTransitionFromBasisToShape(basis_shape, old_revid,
525
                    new_shape, new_revid)
526
527
    def test_content_from_second_parent_is_dropped(self):
528
        left_revid = 'left-parent'
529
        basis_shape = Inventory(root_id=None)
530
        self.add_dir(basis_shape, left_revid, 'root-id', None, '')
531
        self.add_link(basis_shape, left_revid, 'link-id', 'root-id', 'link',
532
            'left-target')
533
        # the right shape has content - file, link, subdir with a child,
534
        # that should all be discarded by the call.
535
        right_revid = 'right-parent'
536
        right_shape = Inventory(root_id=None)
537
        self.add_dir(right_shape, left_revid, 'root-id', None, '')
538
        self.add_link(right_shape, right_revid, 'link-id', 'root-id', 'link',
2865.1.3 by Robert Collins
Review feedback.
539
            'some-target')
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
540
        self.add_dir(right_shape, right_revid, 'subdir-id', 'root-id', 'dir')
541
        self.add_file(right_shape, right_revid, 'file-id', 'subdir-id', 'file',
542
            '2' * 32, 24)
543
        new_revid = 'new-parent'
544
        new_shape = Inventory(root_id=None)
545
        self.add_new_root(new_shape, left_revid, new_revid)
546
        self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
547
            'new-target')
548
        self.assertTransitionFromBasisToShape(basis_shape, left_revid,
549
            new_shape, new_revid, right_revid)
550
551
    def test_parent_id_changed(self):
2865.1.3 by Robert Collins
Review feedback.
552
        # 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.
553
        # that it is handled correctly (that is it keeps the same path)
554
        old_revid = 'old-parent'
555
        basis_shape = Inventory(root_id=None)
556
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
557
        self.add_dir(basis_shape, old_revid, 'orig-parent-id', 'root-id', 'dir')
558
        self.add_dir(basis_shape, old_revid, 'dir-id', 'orig-parent-id', 'dir')
559
        new_revid = 'new-parent'
560
        new_shape = Inventory(root_id=None)
561
        self.add_new_root(new_shape, old_revid, new_revid)
562
        self.add_dir(new_shape, new_revid, 'new-parent-id', 'root-id', 'dir')
563
        self.add_dir(new_shape, new_revid, 'dir-id', 'new-parent-id', 'dir')
564
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
565
            new_shape, new_revid)
566
567
    def test_name_changed(self):
2865.1.3 by Robert Collins
Review feedback.
568
        # test that when the only change to an entry is its name changing that
569
        # 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.
570
        old_revid = 'old-parent'
571
        basis_shape = Inventory(root_id=None)
572
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
573
        self.add_dir(basis_shape, old_revid, 'parent-id', 'root-id', 'origdir')
574
        self.add_dir(basis_shape, old_revid, 'dir-id', 'parent-id', 'olddir')
575
        new_revid = 'new-parent'
576
        new_shape = Inventory(root_id=None)
577
        self.add_new_root(new_shape, old_revid, new_revid)
578
        self.add_dir(new_shape, new_revid, 'parent-id', 'root-id', 'newdir')
579
        self.add_dir(new_shape, new_revid, 'dir-id', 'parent-id', 'newdir')
580
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
581
            new_shape, new_revid)
582
2929.2.2 by Robert Collins
Review feedback on dirstate update_basis_via_delta logic.
583
    def test_parent_child_swap(self):
584
        # test a A->A/B and A/B->A path swap.
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, 'dir-id-A', 'root-id', 'A')
589
        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
590
        self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
591
        new_revid = 'new-parent'
592
        new_shape = Inventory(root_id=None)
593
        self.add_new_root(new_shape, old_revid, new_revid)
594
        self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
595
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'dir-id-B', 'B')
596
        self.add_link(new_shape, new_revid, 'link-id-C', 'dir-id-A', 'C', 'C')
597
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
598
            new_shape, new_revid)
599
3253.2.1 by John Arbash Meinel
Fix moving directories to root nodes.
600
    def test_parent_deleted_child_renamed(self):
601
        # test a A->None and A/B->A.
602
        old_revid = 'old-parent'
603
        basis_shape = Inventory(root_id=None)
604
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
605
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
606
        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
607
        self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
608
        new_revid = 'new-parent'
609
        new_shape = Inventory(root_id=None)
610
        self.add_new_root(new_shape, old_revid, new_revid)
611
        self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
612
        self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
613
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
614
            new_shape, new_revid)
615
616
    def test_dir_to_root(self):
617
        # test a A->''.
618
        old_revid = 'old-parent'
619
        basis_shape = Inventory(root_id=None)
620
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
621
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
622
        self.add_link(basis_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'B')
623
        new_revid = 'new-parent'
624
        new_shape = Inventory(root_id=None)
625
        self.add_dir(new_shape, new_revid, 'dir-id-A', None, '')
626
        self.add_link(new_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'B')
627
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
628
            new_shape, new_revid)
629
2865.1.1 by Robert Collins
Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
630
    def test_path_swap(self):
631
        # test a A->B and B->A path swap.
632
        old_revid = 'old-parent'
633
        basis_shape = Inventory(root_id=None)
634
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
635
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
636
        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'root-id', 'B')
637
        self.add_link(basis_shape, old_revid, 'link-id-C', 'root-id', 'C', 'C')
638
        self.add_link(basis_shape, old_revid, 'link-id-D', 'root-id', 'D', 'D')
639
        self.add_file(basis_shape, old_revid, 'file-id-E', 'root-id', 'E',
640
            '1' * 32, 12)
641
        self.add_file(basis_shape, old_revid, 'file-id-F', 'root-id', 'F',
642
            '2' * 32, 24)
643
        new_revid = 'new-parent'
644
        new_shape = Inventory(root_id=None)
645
        self.add_new_root(new_shape, old_revid, new_revid)
646
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'B')
647
        self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
648
        self.add_link(new_shape, new_revid, 'link-id-C', 'root-id', 'D', 'C')
649
        self.add_link(new_shape, new_revid, 'link-id-D', 'root-id', 'C', 'D')
650
        self.add_file(new_shape, new_revid, 'file-id-E', 'root-id', 'F',
651
            '1' * 32, 12)
652
        self.add_file(new_shape, new_revid, 'file-id-F', 'root-id', 'E',
653
            '2' * 32, 24)
654
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
655
            new_shape, new_revid)
656
657
    def test_adds(self):
658
        # test adding paths and dirs, including adding to a newly added dir.
659
        old_revid = 'old-parent'
660
        basis_shape = Inventory(root_id=None)
661
        # with a root, so its a commit after the first.
662
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
663
        new_revid = 'new-parent'
664
        new_shape = Inventory(root_id=None)
665
        self.add_new_root(new_shape, old_revid, new_revid)
666
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
667
        self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
668
        self.add_file(new_shape, new_revid, 'file-id-C', 'root-id', 'C',
669
            '1' * 32, 12)
670
        self.add_file(new_shape, new_revid, 'file-id-D', 'dir-id-A', 'D',
671
            '2' * 32, 24)
672
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
673
            new_shape, new_revid)
674
675
    def test_removes(self):
676
        # test removing paths, including paths that are within other also
677
        # removed paths.
678
        old_revid = 'old-parent'
679
        basis_shape = Inventory(root_id=None)
680
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
681
        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
682
        self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
683
        self.add_file(basis_shape, old_revid, 'file-id-C', 'root-id', 'C',
684
            '1' * 32, 12)
685
        self.add_file(basis_shape, old_revid, 'file-id-D', 'dir-id-A', 'D',
686
            '2' * 32, 24)
687
        new_revid = 'new-parent'
688
        new_shape = Inventory(root_id=None)
689
        self.add_new_root(new_shape, old_revid, new_revid)
690
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
691
            new_shape, new_revid)
692
693
    def test_move_to_added_dir(self):
694
        old_revid = 'old-parent'
695
        basis_shape = Inventory(root_id=None)
696
        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
697
        self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
698
        new_revid = 'new-parent'
699
        new_shape = Inventory(root_id=None)
700
        self.add_new_root(new_shape, old_revid, new_revid)
701
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
702
        self.add_link(new_shape, new_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
703
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
704
            new_shape, new_revid)
705
706
    def test_move_from_removed_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_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
711
        self.add_link(basis_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
712
        new_revid = 'new-parent'
713
        new_shape = Inventory(root_id=None)
714
        self.add_new_root(new_shape, old_revid, new_revid)
715
        self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
716
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
717
            new_shape, new_revid)
2929.2.1 by Robert Collins
* Commit updates the state of the working tree via a delta rather than
718
719
    def test_move_moves_children_recursively(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_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
725
        self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
726
        new_revid = 'new-parent'
727
        new_shape = Inventory(root_id=None)
728
        self.add_new_root(new_shape, old_revid, new_revid)
729
        # the moved path:
730
        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'B')
731
        # unmoved children.
732
        self.add_dir(new_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
733
        self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
734
        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
735
            new_shape, new_revid)