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