~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/repository_implementations/test_reconcile.py

Merge HEAD.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import bzrlib.errors as errors
22
22
from bzrlib.reconcile import reconcile, Reconciler
23
23
from bzrlib.revision import Revision
 
24
from bzrlib.tests import TestSkipped
24
25
from bzrlib.tests.repository_implementations.test_repository import TestCaseWithRepository
25
26
from bzrlib.transport import get_transport
26
27
from bzrlib.tree import EmptyTree
 
28
from bzrlib.uncommit import uncommit
27
29
from bzrlib.workingtree import WorkingTree
28
30
 
29
31
 
30
 
class TestsNeedingReweave(TestCaseWithRepository):
 
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):
31
52
 
32
53
    def setUp(self):
33
54
        super(TestsNeedingReweave, self).setUp()
34
55
        
35
56
        t = get_transport(self.get_url())
36
57
        # an empty inventory with no revision for testing with.
 
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.
37
63
        # this is referenced by 'references_missing' to let us test
38
64
        # that all the cached data is correctly converted into ghost links
39
65
        # and the referenced inventory still cleaned.
40
 
        repo = self.make_repository('inventory_without_revision')
 
66
        repo = self.make_repository('inventory_without_revision_and_ghost')
41
67
        inv = EmptyTree().inventory
42
68
        repo.add_inventory('missing', inv, [])
43
69
        sha1 = repo.add_inventory('references_missing', inv, ['missing'])
76
102
        rev.parent_ids = []
77
103
        repo.add_revision('the_ghost', rev)
78
104
 
79
 
    def test_reweave_empty(self):
 
105
    def checkEmptyReconcile(self, **kwargs):
 
106
        """Check a reconcile on an empty repository."""
80
107
        self.make_repository('empty')
81
108
        d = bzrlib.bzrdir.BzrDir.open('empty')
82
109
        # calling on a empty repository should do nothing
83
 
        reconciler = d.find_repository().reconcile()
 
110
        reconciler = d.find_repository().reconcile(**kwargs)
84
111
        # no inconsistent parents should have been found
85
112
        self.assertEqual(0, reconciler.inconsistent_parents)
86
113
        # and no garbage inventories
87
114
        self.assertEqual(0, reconciler.garbage_inventories)
88
115
        # and no backup weave should have been needed/made.
89
 
        repo = d.open_repository()
90
 
        self.assertRaises(errors.NoSuchFile,
91
 
                          repo.control_weaves.get_weave,
92
 
                          'inventory.backup',
93
 
                          repo.get_transaction())
94
 
 
95
 
    def test_reweave_inventory_without_revision_reconcile(self):
 
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)
 
125
 
 
126
    def test_convenience_reconcile_inventory_without_revision_reconcile(self):
96
127
        # smoke test for the all in one ui tool
97
128
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision')
98
129
        reconcile(d)
99
130
        # now the backup should have it but not the current inventory
100
131
        repo = d.open_repository()
101
 
        backup = repo.control_weaves.get_weave('inventory.backup',
102
 
                                               repo.get_transaction())
103
 
        self.assertTrue('missing' in backup.versions())
104
 
        self.assertRaises(errors.RevisionNotPresent,
105
 
                          repo.get_inventory, 'missing')
106
 
 
107
 
    def test_reweave_inventory_without_revision_reconciler(self):
108
 
        # smoke test for the all in one Reconciler class,
109
 
        # other tests use the lower level repo.reconcile()
110
 
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision')
111
 
        reconciler = Reconciler(d)
112
 
        reconciler.reconcile()
113
 
        # no inconsistent parents should have been found
114
 
        self.assertEqual(1, reconciler.inconsistent_parents)
115
 
        # and one garbage inventories
116
 
        self.assertEqual(1, reconciler.garbage_inventories)
117
 
        # now the backup should have it but not the current inventory
118
 
        repo = d.open_repository()
119
 
        backup = repo.control_weaves.get_weave('inventory.backup',
120
 
                                               repo.get_transaction())
121
 
        self.assertTrue('missing' in backup.versions())
122
 
        self.assertRaises(errors.RevisionNotPresent,
123
 
                          repo.get_inventory, 'missing')
 
132
        self.check_missing_was_removed(repo)
124
133
 
125
134
    def test_reweave_inventory_without_revision(self):
126
 
        # actual low level test.
 
135
        # an excess inventory on its own is only reconciled by using thorough
127
136
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision')
128
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)
 
145
 
 
146
    def check_thorough_reweave_missing_revision(self, aBzrDir, reconcile,
 
147
            **kwargs):
 
148
        # actual low level test.
 
149
        repo = aBzrDir.open_repository()
129
150
        if ([None, 'missing', 'references_missing'] 
130
151
            != repo.get_ancestry('references_missing')):
131
152
            # the repo handles ghosts without corruption, so reconcile has
133
154
            expected_inconsistent_parents = 0
134
155
        else:
135
156
            expected_inconsistent_parents = 1
136
 
        reconciler = repo.reconcile()
 
157
        reconciler = reconcile(**kwargs)
137
158
        # some number of inconsistent parents should have been found
138
159
        self.assertEqual(expected_inconsistent_parents,
139
160
                         reconciler.inconsistent_parents)
140
161
        # and one garbage inventories
141
162
        self.assertEqual(1, reconciler.garbage_inventories)
142
163
        # now the backup should have it but not the current inventory
143
 
        repo = d.open_repository()
144
 
        backup = repo.control_weaves.get_weave('inventory.backup',
145
 
                                               repo.get_transaction())
146
 
        self.assertTrue('missing' in backup.versions())
147
 
        self.assertRaises(errors.RevisionNotPresent,
148
 
                          repo.get_inventory, 'missing')
 
164
        repo = aBzrDir.open_repository()
 
165
        self.check_missing_was_removed(repo)
149
166
        # and the parent list for 'references_missing' should have that
150
167
        # revision a ghost now.
151
168
        self.assertEqual([None, 'references_missing'],
152
169
                         repo.get_ancestry('references_missing'))
153
170
 
 
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
 
 
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()
 
181
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision_and_ghost')
 
182
        def reconcile():
 
183
            reconciler = Reconciler(d)
 
184
            reconciler.reconcile()
 
185
            return reconciler
 
186
        self.check_thorough_reweave_missing_revision(d, reconcile)
 
187
 
 
188
    def test_reweave_inventory_without_revision_and_ghost(self):
 
189
        # actual low level test.
 
190
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision_and_ghost')
 
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
 
154
197
    def test_reweave_inventory_preserves_a_revision_with_ghosts(self):
155
198
        d = bzrlib.bzrdir.BzrDir.open('inventory_one_ghost')
156
 
        reconciler = d.open_repository().reconcile()
 
199
        reconciler = d.open_repository().reconcile(thorough=True)
157
200
        # no inconsistent parents should have been found: 
158
201
        # the lack of a parent for ghost is normal
159
202
        self.assertEqual(0, reconciler.inconsistent_parents)
174
217
            return
175
218
        self.assertEqual([None, 'ghost'], ghost_ancestry)
176
219
        reconciler = repo.reconcile()
 
220
        # this is a data corrupting error, so a normal reconcile should fix it.
177
221
        # one inconsistent parents should have been found : the
178
222
        # available but not reference parent for ghost.
179
223
        self.assertEqual(1, reconciler.inconsistent_parents)
185
229
        repo.get_inventory('the_ghost')
186
230
        self.assertEqual([None, 'the_ghost', 'ghost'], repo.get_ancestry('ghost'))
187
231
        self.assertEqual([None, 'the_ghost'], repo.get_ancestry('the_ghost'))
 
232
 
 
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)
 
305
        # and no garbage inventories
 
306
        self.assertEqual(0, reconciler.garbage_inventories)
 
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))