~bzr-pqm/bzr/bzr.dev

1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
1
# Copyright (C) 2006 by Canonical Ltd
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tests for reconiliation of repositories."""
18
19
20
import bzrlib
21
import bzrlib.errors as errors
1594.2.7 by Robert Collins
Add versionedfile.fix_parents api for correcting data post hoc.
22
from bzrlib.reconcile import reconcile, Reconciler
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
23
from bzrlib.revision import Revision
1692.1.2 by Robert Collins
Teach reconcile to check the left-most parent is correct in the revision graph.
24
from bzrlib.tests import TestSkipped
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
25
from bzrlib.tests.repository_implementations.test_repository import TestCaseWithRepository
26
from bzrlib.transport import get_transport
27
from bzrlib.tree import EmptyTree
1692.1.2 by Robert Collins
Teach reconcile to check the left-most parent is correct in the revision graph.
28
from bzrlib.uncommit import uncommit
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
29
from bzrlib.workingtree import WorkingTree
30
31
1692.1.2 by Robert Collins
Teach reconcile to check the left-most parent is correct in the revision graph.
32
class TestReconcile(TestCaseWithRepository):
33
34
    def checkUnreconciled(self, d, reconciler):
35
        """Check that d did not get reconciled."""
36
        # nothing should have been fixed yet:
37
        self.assertEqual(0, reconciler.inconsistent_parents)
38
        # and no garbage inventories
39
        self.assertEqual(0, reconciler.garbage_inventories)
40
        self.checkNoBackupInventory(d)
41
42
    def checkNoBackupInventory(self, aBzrDir):
43
        """Check that there is no backup inventory in aBzrDir."""
44
        repo = aBzrDir.open_repository()
45
        self.assertRaises(errors.NoSuchFile,
46
                          repo.control_weaves.get_weave,
47
                          'inventory.backup',
48
                          repo.get_transaction())
49
50
51
class TestsNeedingReweave(TestReconcile):
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
52
53
    def setUp(self):
1570.1.8 by Robert Collins
Only reconcile if doing so will perform gc or correct ancestry.
54
        super(TestsNeedingReweave, self).setUp()
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
55
        
56
        t = get_transport(self.get_url())
57
        # an empty inventory with no revision for testing with.
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
58
        repo = self.make_repository('inventory_without_revision')
59
        inv = EmptyTree().inventory
60
        repo.add_inventory('missing', inv, [])
61
62
        # an empty inventory with no revision for testing with.
1570.1.14 by Robert Collins
Enforce repository consistency during 'fetch' operations.
63
        # this is referenced by 'references_missing' to let us test
64
        # that all the cached data is correctly converted into ghost links
65
        # and the referenced inventory still cleaned.
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
66
        repo = self.make_repository('inventory_without_revision_and_ghost')
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
67
        inv = EmptyTree().inventory
68
        repo.add_inventory('missing', inv, [])
1570.1.14 by Robert Collins
Enforce repository consistency during 'fetch' operations.
69
        sha1 = repo.add_inventory('references_missing', inv, ['missing'])
70
        rev = Revision(timestamp=0,
71
                       timezone=None,
72
                       committer="Foo Bar <foo@example.com>",
73
                       message="Message",
74
                       inventory_sha1=sha1,
75
                       revision_id='references_missing')
76
        rev.parent_ids = ['missing']
77
        repo.add_revision('references_missing', rev)
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
78
79
        # a inventory with no parents and the revision has parents..
80
        # i.e. a ghost.
1570.1.8 by Robert Collins
Only reconcile if doing so will perform gc or correct ancestry.
81
        repo = self.make_repository('inventory_one_ghost')
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
82
        sha1 = repo.add_inventory('ghost', inv, [])
83
        rev = Revision(timestamp=0,
84
                       timezone=None,
85
                       committer="Foo Bar <foo@example.com>",
86
                       message="Message",
87
                       inventory_sha1=sha1,
88
                       revision_id='ghost')
89
        rev.parent_ids = ['the_ghost']
90
        repo.add_revision('ghost', rev)
91
         
92
        # a inventory with a ghost that can be corrected now.
93
        t.copy_tree('inventory_one_ghost', 'inventory_ghost_present')
94
        repo = bzrlib.repository.Repository.open('inventory_ghost_present')
95
        sha1 = repo.add_inventory('the_ghost', inv, [])
96
        rev = Revision(timestamp=0,
97
                       timezone=None,
98
                       committer="Foo Bar <foo@example.com>",
99
                       message="Message",
100
                       inventory_sha1=sha1,
101
                       revision_id='the_ghost')
102
        rev.parent_ids = []
103
        repo.add_revision('the_ghost', rev)
104
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
105
    def checkEmptyReconcile(self, **kwargs):
106
        """Check a reconcile on an empty repository."""
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
107
        self.make_repository('empty')
108
        d = bzrlib.bzrdir.BzrDir.open('empty')
1570.1.14 by Robert Collins
Enforce repository consistency during 'fetch' operations.
109
        # calling on a empty repository should do nothing
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
110
        reconciler = d.find_repository().reconcile(**kwargs)
1570.1.8 by Robert Collins
Only reconcile if doing so will perform gc or correct ancestry.
111
        # no inconsistent parents should have been found
112
        self.assertEqual(0, reconciler.inconsistent_parents)
113
        # and no garbage inventories
114
        self.assertEqual(0, reconciler.garbage_inventories)
115
        # and no backup weave should have been needed/made.
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
116
        self.checkNoBackupInventory(d)
117
118
    def test_reconile_empty(self):
119
        # in an empty repo, theres nothing to do.
120
        self.checkEmptyReconcile()
121
122
    def test_reconcile_empty_thorough(self):
123
        # reconcile should accept thorough=True
124
        self.checkEmptyReconcile(thorough=True)
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
125
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
126
    def test_convenience_reconcile_inventory_without_revision_reconcile(self):
1570.1.14 by Robert Collins
Enforce repository consistency during 'fetch' operations.
127
        # smoke test for the all in one ui tool
128
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision')
129
        reconcile(d)
130
        # now the backup should have it but not the current inventory
131
        repo = d.open_repository()
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
132
        self.check_missing_was_removed(repo)
133
134
    def test_reweave_inventory_without_revision(self):
135
        # an excess inventory on its own is only reconciled by using thorough
136
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision')
137
        repo = d.open_repository()
138
        self.checkUnreconciled(d, repo.reconcile())
139
        reconciler = repo.reconcile(thorough=True)
140
        # no bad parents
141
        self.assertEqual(0, reconciler.inconsistent_parents)
142
        # and one garbage inventoriy
143
        self.assertEqual(1, reconciler.garbage_inventories)
144
        self.check_missing_was_removed(repo)
1570.1.14 by Robert Collins
Enforce repository consistency during 'fetch' operations.
145
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
146
    def check_thorough_reweave_missing_revision(self, aBzrDir, reconcile,
147
            **kwargs):
1570.1.14 by Robert Collins
Enforce repository consistency during 'fetch' operations.
148
        # actual low level test.
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
149
        repo = aBzrDir.open_repository()
1594.2.9 by Robert Collins
Teach Knit repositories how to handle ghosts without corrupting at all.
150
        if ([None, 'missing', 'references_missing'] 
151
            != repo.get_ancestry('references_missing')):
152
            # the repo handles ghosts without corruption, so reconcile has
153
            # nothing to do here
154
            expected_inconsistent_parents = 0
155
        else:
156
            expected_inconsistent_parents = 1
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
157
        reconciler = reconcile(**kwargs)
1594.2.9 by Robert Collins
Teach Knit repositories how to handle ghosts without corrupting at all.
158
        # some number of inconsistent parents should have been found
159
        self.assertEqual(expected_inconsistent_parents,
160
                         reconciler.inconsistent_parents)
1570.1.8 by Robert Collins
Only reconcile if doing so will perform gc or correct ancestry.
161
        # and one garbage inventories
162
        self.assertEqual(1, reconciler.garbage_inventories)
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
163
        # now the backup should have it but not the current inventory
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
164
        repo = aBzrDir.open_repository()
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
165
        self.check_missing_was_removed(repo)
1570.1.14 by Robert Collins
Enforce repository consistency during 'fetch' operations.
166
        # and the parent list for 'references_missing' should have that
167
        # revision a ghost now.
168
        self.assertEqual([None, 'references_missing'],
169
                         repo.get_ancestry('references_missing'))
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
170
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
171
    def check_missing_was_removed(self, repo):
172
        backup = repo.control_weaves.get_weave('inventory.backup',
173
                                               repo.get_transaction())
174
        self.assertTrue('missing' in backup.versions())
175
        self.assertRaises(errors.RevisionNotPresent,
176
                          repo.get_inventory, 'missing')
177
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
178
    def test_reweave_inventory_without_revision_reconciler(self):
179
        # smoke test for the all in one Reconciler class,
180
        # other tests use the lower level repo.reconcile()
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
181
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision_and_ghost')
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
182
        def reconcile():
183
            reconciler = Reconciler(d)
184
            reconciler.reconcile()
185
            return reconciler
186
        self.check_thorough_reweave_missing_revision(d, reconcile)
187
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
188
    def test_reweave_inventory_without_revision_and_ghost(self):
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
189
        # actual low level test.
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
190
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision_and_ghost')
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
191
        repo = d.open_repository()
192
        # nothing should have been altered yet : inventories without
193
        # revisions are not data loss incurring for current format
194
        self.check_thorough_reweave_missing_revision(d, repo.reconcile,
195
            thorough=True)
196
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
197
    def test_reweave_inventory_preserves_a_revision_with_ghosts(self):
198
        d = bzrlib.bzrdir.BzrDir.open('inventory_one_ghost')
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
199
        reconciler = d.open_repository().reconcile(thorough=True)
1570.1.8 by Robert Collins
Only reconcile if doing so will perform gc or correct ancestry.
200
        # no inconsistent parents should have been found: 
201
        # the lack of a parent for ghost is normal
202
        self.assertEqual(0, reconciler.inconsistent_parents)
203
        # and one garbage inventories
204
        self.assertEqual(0, reconciler.garbage_inventories)
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
205
        # now the current inventory should still have 'ghost'
206
        repo = d.open_repository()
207
        repo.get_inventory('ghost')
208
        self.assertEqual([None, 'ghost'], repo.get_ancestry('ghost'))
209
        
210
    def test_reweave_inventory_fixes_ancestryfor_a_present_ghost(self):
211
        d = bzrlib.bzrdir.BzrDir.open('inventory_ghost_present')
212
        repo = d.open_repository()
1594.2.9 by Robert Collins
Teach Knit repositories how to handle ghosts without corrupting at all.
213
        ghost_ancestry = repo.get_ancestry('ghost')
214
        if ghost_ancestry == [None, 'the_ghost', 'ghost']:
215
            # the repo handles ghosts without corruption, so reconcile has
216
            # nothing to do
217
            return
218
        self.assertEqual([None, 'ghost'], ghost_ancestry)
1594.2.7 by Robert Collins
Add versionedfile.fix_parents api for correcting data post hoc.
219
        reconciler = repo.reconcile()
1692.1.3 by Robert Collins
Finish the reconcile tweak: filled in ghosts are a data loss issue and need to be checked during fast reconciles.
220
        # this is a data corrupting error, so a normal reconcile should fix it.
1570.1.8 by Robert Collins
Only reconcile if doing so will perform gc or correct ancestry.
221
        # one inconsistent parents should have been found : the
222
        # available but not reference parent for ghost.
223
        self.assertEqual(1, reconciler.inconsistent_parents)
224
        # and no garbage inventories
225
        self.assertEqual(0, reconciler.garbage_inventories)
1570.1.2 by Robert Collins
Import bzrtools' 'fix' command as 'bzr reconcile.'
226
        # now the current inventory should still have 'ghost'
227
        repo = d.open_repository()
228
        repo.get_inventory('ghost')
229
        repo.get_inventory('the_ghost')
230
        self.assertEqual([None, 'the_ghost', 'ghost'], repo.get_ancestry('ghost'))
231
        self.assertEqual([None, 'the_ghost'], repo.get_ancestry('the_ghost'))
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
232
1692.1.2 by Robert Collins
Teach reconcile to check the left-most parent is correct in the revision graph.
233
234
class TestReconcileWithIncorrectRevisionCache(TestReconcile):
235
    """Ancestry data gets cached in knits and weaves should be reconcilable.
236
237
    This class tests that reconcile can correct invalid caches (such as after
238
    a reconcile).
239
    """
240
241
    def setUp(self):
242
        super(TestReconcileWithIncorrectRevisionCache, self).setUp()
243
        
244
        t = get_transport(self.get_url())
245
        # we need a revision with two parents in the wrong order
246
        # which should trigger reinsertion.
247
        # and another with the first one correct but the other two not
248
        # which should not trigger reinsertion.
249
        # these need to be in different repositories so that we don't
250
        # trigger a reconcile based on the other case.
251
        # there is no api to construct a broken knit repository at
252
        # this point. if we ever encounter a bad graph in a knit repo
253
        # we should add a lower level api to allow constructing such cases.
254
        
255
        # first off the common logic:
256
        tree = self.make_branch_and_tree('wrong-first-parent')
257
        tree.commit('1', rev_id='1')
258
        uncommit(tree.branch, tree=tree)
259
        tree.commit('2', rev_id='2')
260
        uncommit(tree.branch, tree=tree)
261
        tree.commit('3', rev_id='3')
262
        uncommit(tree.branch, tree=tree)
263
        repo_secondary = tree.bzrdir.clone(
264
            'reversed-secondary-parents').open_repository()
265
266
        # now setup the wrong-first parent case
267
        repo = tree.branch.repository
268
        inv = EmptyTree().inventory
269
        sha1 = repo.add_inventory('wrong-first-parent', inv, ['2', '1'])
270
        rev = Revision(timestamp=0,
271
                       timezone=None,
272
                       committer="Foo Bar <foo@example.com>",
273
                       message="Message",
274
                       inventory_sha1=sha1,
275
                       revision_id='wrong-first-parent')
276
        rev.parent_ids = ['1', '2']
277
        repo.add_revision('wrong-first-parent', rev)
278
279
        # now setup the wrong-secondary parent case
280
        repo = repo_secondary
281
        inv = EmptyTree().inventory
282
        sha1 = repo.add_inventory('wrong-secondary-parent', inv, ['1', '3', '2'])
283
        rev = Revision(timestamp=0,
284
                       timezone=None,
285
                       committer="Foo Bar <foo@example.com>",
286
                       message="Message",
287
                       inventory_sha1=sha1,
288
                       revision_id='wrong-secondary-parent')
289
        rev.parent_ids = ['1', '2', '3']
290
        repo.add_revision('wrong-secondary-parent', rev)
291
292
    def test_reconcile_wrong_order(self):
293
        # a wrong order in primary parents is optionally correctable
294
        d = bzrlib.bzrdir.BzrDir.open('wrong-first-parent')
295
        repo = d.open_repository()
296
        g = repo.get_revision_graph()
297
        if g['wrong-first-parent'] == ['1', '2']:
298
            raise TestSkipped('wrong-first-parent is not setup for testing')
299
        self.checkUnreconciled(d, repo.reconcile())
300
        # nothing should have been altered yet : inventories without
301
        # revisions are not data loss incurring for current format
302
        reconciler = repo.reconcile(thorough=True)
303
        # these show up as inconsistent parents
304
        self.assertEqual(1, reconciler.inconsistent_parents)
1692.1.1 by Robert Collins
* Repository.reconcile now takes a thorough keyword parameter to allow
305
        # and no garbage inventories
306
        self.assertEqual(0, reconciler.garbage_inventories)
1692.1.2 by Robert Collins
Teach reconcile to check the left-most parent is correct in the revision graph.
307
        # and should have been fixed:
308
        g = repo.get_revision_graph()
309
        self.assertEqual(['1', '2'], g['wrong-first-parent'])
310
311
    def test_reconcile_wrong_order_secondary(self):
312
        # a wrong order in secondary parents is ignored.
313
        d = bzrlib.bzrdir.BzrDir.open('reversed-secondary-parents')
314
        repo = d.open_repository()
315
        self.checkUnreconciled(d, repo.reconcile())
316
        self.checkUnreconciled(d, repo.reconcile(thorough=True))