/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

First attempt to merge .dev and resolve the conflicts (but tests are 
failing)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
"""Tests for reconiliation of repositories."""
 
17
"""Tests for reconciliation of repositories."""
18
18
 
19
19
 
20
20
import bzrlib
21
21
import bzrlib.errors as errors
22
22
from bzrlib.inventory import Inventory
23
23
from bzrlib.reconcile import reconcile, Reconciler
 
24
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
24
25
from bzrlib.revision import Revision
25
 
from bzrlib.tests import TestSkipped
26
 
from bzrlib.tests.repository_implementations.test_repository import TestCaseWithRepository
 
26
from bzrlib.tests import TestSkipped, TestNotApplicable
 
27
from bzrlib.tests.repository_implementations.helpers import (
 
28
    TestCaseWithBrokenRevisionIndex,
 
29
    )
 
30
from bzrlib.tests.repository_implementations.test_repository import (
 
31
    TestCaseWithRepository,
 
32
    )
27
33
from bzrlib.transport import get_transport
28
34
from bzrlib.uncommit import uncommit
29
 
from bzrlib.workingtree import WorkingTree
30
35
 
31
36
 
32
37
class TestReconcile(TestCaseWithRepository):
42
47
    def checkNoBackupInventory(self, aBzrDir):
43
48
        """Check that there is no backup inventory in aBzrDir."""
44
49
        repo = aBzrDir.open_repository()
45
 
        self.assertRaises(errors.NoSuchFile,
46
 
                          repo.control_weaves.get_weave,
47
 
                          'inventory.backup',
48
 
                          repo.get_transaction())
 
50
        # Remote repository, and possibly others, do not have 
 
51
        # _transport.
 
52
        if getattr(repo, '_transport', None) is not None:
 
53
            for path in repo._transport.list_dir('.'):
 
54
                self.assertFalse('inventory.backup' in path)
49
55
 
50
56
 
51
57
class TestsNeedingReweave(TestReconcile):
56
62
        t = get_transport(self.get_url())
57
63
        # an empty inventory with no revision for testing with.
58
64
        repo = self.make_repository('inventory_without_revision')
 
65
        repo.lock_write()
 
66
        repo.start_write_group()
59
67
        inv = Inventory(revision_id='missing')
60
68
        inv.root.revision = 'missing'
61
69
        repo.add_inventory('missing', inv, [])
 
70
        repo.commit_write_group()
 
71
        repo.unlock()
62
72
 
 
73
        def add_commit(repo, revision_id, parent_ids):
 
74
            repo.lock_write()
 
75
            repo.start_write_group()
 
76
            inv = Inventory(revision_id=revision_id)
 
77
            inv.root.revision = revision_id
 
78
            root_id = inv.root.file_id
 
79
            sha1 = repo.add_inventory(revision_id, inv, parent_ids)
 
80
            repo.texts.add_lines((root_id, revision_id), [], [])
 
81
            rev = bzrlib.revision.Revision(timestamp=0,
 
82
                                           timezone=None,
 
83
                                           committer="Foo Bar <foo@example.com>",
 
84
                                           message="Message",
 
85
                                           inventory_sha1=sha1,
 
86
                                           revision_id=revision_id)
 
87
            rev.parent_ids = parent_ids
 
88
            repo.add_revision(revision_id, rev)
 
89
            repo.commit_write_group()
 
90
            repo.unlock()
63
91
        # an empty inventory with no revision for testing with.
64
92
        # this is referenced by 'references_missing' to let us test
65
93
        # that all the cached data is correctly converted into ghost links
66
94
        # and the referenced inventory still cleaned.
67
95
        repo = self.make_repository('inventory_without_revision_and_ghost')
 
96
        repo.lock_write()
 
97
        repo.start_write_group()
68
98
        repo.add_inventory('missing', inv, [])
69
 
        inv = Inventory(revision_id='references_missing')
70
 
        inv.root.revision = 'references_missing'
71
 
        sha1 = repo.add_inventory('references_missing', inv, ['missing'])
72
 
        rev = Revision(timestamp=0,
73
 
                       timezone=None,
74
 
                       committer="Foo Bar <foo@example.com>",
75
 
                       message="Message",
76
 
                       inventory_sha1=sha1,
77
 
                       revision_id='references_missing')
78
 
        rev.parent_ids = ['missing']
79
 
        repo.add_revision('references_missing', rev)
 
99
        repo.commit_write_group()
 
100
        repo.unlock()
 
101
        add_commit(repo, 'references_missing', ['missing'])
80
102
 
81
103
        # a inventory with no parents and the revision has parents..
82
104
        # i.e. a ghost.
83
105
        repo = self.make_repository('inventory_one_ghost')
84
 
        inv = Inventory(revision_id='ghost')
85
 
        inv.root.revision = 'ghost'
86
 
        sha1 = repo.add_inventory('ghost', inv, [])
87
 
        rev = Revision(timestamp=0,
88
 
                       timezone=None,
89
 
                       committer="Foo Bar <foo@example.com>",
90
 
                       message="Message",
91
 
                       inventory_sha1=sha1,
92
 
                       revision_id='ghost')
93
 
        rev.parent_ids = ['the_ghost']
94
 
        repo.add_revision('ghost', rev)
 
106
        add_commit(repo, 'ghost', ['the_ghost'])
95
107
         
96
108
        # a inventory with a ghost that can be corrected now.
97
109
        t.copy_tree('inventory_one_ghost', 'inventory_ghost_present')
98
 
        repo = bzrlib.repository.Repository.open('inventory_ghost_present')
99
 
        inv = Inventory(revision_id='the_ghost')
100
 
        inv.root.revision = 'the_ghost'
101
 
        sha1 = repo.add_inventory('the_ghost', inv, [])
102
 
        rev = Revision(timestamp=0,
103
 
                       timezone=None,
104
 
                       committer="Foo Bar <foo@example.com>",
105
 
                       message="Message",
106
 
                       inventory_sha1=sha1,
107
 
                       revision_id='the_ghost')
108
 
        rev.parent_ids = []
109
 
        repo.add_revision('the_ghost', rev)
 
110
        bzrdir_url = self.get_url('inventory_ghost_present')
 
111
        bzrdir = bzrlib.bzrdir.BzrDir.open(bzrdir_url)
 
112
        repo = bzrdir.open_repository()
 
113
        add_commit(repo, 'the_ghost', [])
110
114
 
111
115
    def checkEmptyReconcile(self, **kwargs):
112
116
        """Check a reconcile on an empty repository."""
113
117
        self.make_repository('empty')
114
 
        d = bzrlib.bzrdir.BzrDir.open('empty')
 
118
        d = bzrlib.bzrdir.BzrDir.open(self.get_url('empty'))
115
119
        # calling on a empty repository should do nothing
116
120
        reconciler = d.find_repository().reconcile(**kwargs)
117
121
        # no inconsistent parents should have been found
121
125
        # and no backup weave should have been needed/made.
122
126
        self.checkNoBackupInventory(d)
123
127
 
124
 
    def test_reconile_empty(self):
 
128
    def test_reconcile_empty(self):
125
129
        # in an empty repo, theres nothing to do.
126
130
        self.checkEmptyReconcile()
127
131
 
 
132
    def test_repo_has_reconcile_does_inventory_gc_attribute(self):
 
133
        repo = self.make_repository('repo')
 
134
        self.assertNotEqual(None, repo._reconcile_does_inventory_gc)
 
135
 
128
136
    def test_reconcile_empty_thorough(self):
129
137
        # reconcile should accept thorough=True
130
138
        self.checkEmptyReconcile(thorough=True)
131
139
 
132
140
    def test_convenience_reconcile_inventory_without_revision_reconcile(self):
133
141
        # smoke test for the all in one ui tool
134
 
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision')
135
 
        reconcile(d)
 
142
        bzrdir_url = self.get_url('inventory_without_revision')
 
143
        bzrdir = bzrlib.bzrdir.BzrDir.open(bzrdir_url)
 
144
        repo = bzrdir.open_repository()
 
145
        if not repo._reconcile_does_inventory_gc:
 
146
            raise TestSkipped('Irrelevant test')
 
147
        reconcile(bzrdir)
136
148
        # now the backup should have it but not the current inventory
137
 
        repo = d.open_repository()
 
149
        repo = bzrdir.open_repository()
138
150
        self.check_missing_was_removed(repo)
139
151
 
140
152
    def test_reweave_inventory_without_revision(self):
141
153
        # an excess inventory on its own is only reconciled by using thorough
142
 
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision')
 
154
        d_url = self.get_url('inventory_without_revision')
 
155
        d = bzrlib.bzrdir.BzrDir.open(d_url)
143
156
        repo = d.open_repository()
 
157
        if not repo._reconcile_does_inventory_gc:
 
158
            raise TestSkipped('Irrelevant test')
144
159
        self.checkUnreconciled(d, repo.reconcile())
145
160
        reconciler = repo.reconcile(thorough=True)
146
161
        # no bad parents
147
162
        self.assertEqual(0, reconciler.inconsistent_parents)
148
 
        # and one garbage inventoriy
 
163
        # and one garbage inventory
149
164
        self.assertEqual(1, reconciler.garbage_inventories)
150
165
        self.check_missing_was_removed(repo)
151
166
 
153
168
            **kwargs):
154
169
        # actual low level test.
155
170
        repo = aBzrDir.open_repository()
156
 
        if ([None, 'missing', 'references_missing'] 
 
171
        if ([None, 'missing', 'references_missing']
157
172
            != repo.get_ancestry('references_missing')):
158
173
            # the repo handles ghosts without corruption, so reconcile has
159
 
            # nothing to do here
 
174
            # nothing to do here. Specifically, this test has the inventory
 
175
            # 'missing' present and the revision 'missing' missing, so clearly
 
176
            # 'missing' cannot be reported in the present ancestry -> missing
 
177
            # is something that can be filled as a ghost.
160
178
            expected_inconsistent_parents = 0
161
179
        else:
162
180
            expected_inconsistent_parents = 1
175
193
                         repo.get_ancestry('references_missing'))
176
194
 
177
195
    def check_missing_was_removed(self, repo):
178
 
        backup = repo.control_weaves.get_weave('inventory.backup',
179
 
                                               repo.get_transaction())
180
 
        self.assertTrue('missing' in backup.versions())
181
 
        self.assertRaises(errors.RevisionNotPresent,
182
 
                          repo.get_inventory, 'missing')
 
196
        if repo._reconcile_backsup_inventory:
 
197
            backed_up = False
 
198
            for path in repo._transport.list_dir('.'):
 
199
                if 'inventory.backup' in path:
 
200
                    backed_up = True
 
201
            self.assertTrue(backed_up)
 
202
            # Not clear how to do this at an interface level:
 
203
            # self.assertTrue('missing' in backup.versions())
 
204
        self.assertRaises(errors.NoSuchRevision, repo.get_inventory, 'missing')
183
205
 
184
206
    def test_reweave_inventory_without_revision_reconciler(self):
185
207
        # smoke test for the all in one Reconciler class,
186
208
        # other tests use the lower level repo.reconcile()
187
 
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision_and_ghost')
 
209
        d_url = self.get_url('inventory_without_revision_and_ghost')
 
210
        d = bzrlib.bzrdir.BzrDir.open(d_url)
 
211
        if not d.open_repository()._reconcile_does_inventory_gc:
 
212
            raise TestSkipped('Irrelevant test')
188
213
        def reconcile():
189
214
            reconciler = Reconciler(d)
190
215
            reconciler.reconcile()
193
218
 
194
219
    def test_reweave_inventory_without_revision_and_ghost(self):
195
220
        # actual low level test.
196
 
        d = bzrlib.bzrdir.BzrDir.open('inventory_without_revision_and_ghost')
 
221
        d_url = self.get_url('inventory_without_revision_and_ghost')
 
222
        d = bzrlib.bzrdir.BzrDir.open(d_url)
197
223
        repo = d.open_repository()
 
224
        if not repo._reconcile_does_inventory_gc:
 
225
            raise TestSkipped('Irrelevant test')
198
226
        # nothing should have been altered yet : inventories without
199
227
        # revisions are not data loss incurring for current format
200
228
        self.check_thorough_reweave_missing_revision(d, repo.reconcile,
201
229
            thorough=True)
202
230
 
203
231
    def test_reweave_inventory_preserves_a_revision_with_ghosts(self):
204
 
        d = bzrlib.bzrdir.BzrDir.open('inventory_one_ghost')
 
232
        d = bzrlib.bzrdir.BzrDir.open(self.get_url('inventory_one_ghost'))
205
233
        reconciler = d.open_repository().reconcile(thorough=True)
206
234
        # no inconsistent parents should have been found: 
207
235
        # the lack of a parent for ghost is normal
214
242
        self.assertEqual([None, 'ghost'], repo.get_ancestry('ghost'))
215
243
        
216
244
    def test_reweave_inventory_fixes_ancestryfor_a_present_ghost(self):
217
 
        d = bzrlib.bzrdir.BzrDir.open('inventory_ghost_present')
 
245
        d = bzrlib.bzrdir.BzrDir.open(self.get_url('inventory_ghost_present'))
218
246
        repo = d.open_repository()
219
247
        ghost_ancestry = repo.get_ancestry('ghost')
220
248
        if ghost_ancestry == [None, 'the_ghost', 'ghost']:
245
273
    """
246
274
 
247
275
    def setUp(self):
 
276
        self.reduceLockdirTimeout()
248
277
        super(TestReconcileWithIncorrectRevisionCache, self).setUp()
249
278
        
250
279
        t = get_transport(self.get_url())
260
289
        
261
290
        # first off the common logic:
262
291
        tree = self.make_branch_and_tree('wrong-first-parent')
263
 
        tree.commit('1', rev_id='1')
264
 
        uncommit(tree.branch, tree=tree)
265
 
        tree.commit('2', rev_id='2')
266
 
        uncommit(tree.branch, tree=tree)
267
 
        tree.commit('3', rev_id='3')
268
 
        uncommit(tree.branch, tree=tree)
269
 
        repo_secondary = tree.bzrdir.clone(
270
 
            'reversed-secondary-parents').open_repository()
 
292
        second_tree = self.make_branch_and_tree('reversed-secondary-parents')
 
293
        for t in [tree, second_tree]:
 
294
            t.commit('1', rev_id='1')
 
295
            uncommit(t.branch, tree=t)
 
296
            t.commit('2', rev_id='2')
 
297
            uncommit(t.branch, tree=t)
 
298
            t.commit('3', rev_id='3')
 
299
            uncommit(t.branch, tree=t)
 
300
        #second_tree = self.make_branch_and_tree('reversed-secondary-parents')
 
301
        #second_tree.pull(tree) # XXX won't copy the repo?
 
302
        repo_secondary = second_tree.branch.repository
271
303
 
272
304
        # now setup the wrong-first parent case
273
305
        repo = tree.branch.repository
 
306
        repo.lock_write()
 
307
        repo.start_write_group()
274
308
        inv = Inventory(revision_id='wrong-first-parent')
275
309
        inv.root.revision = 'wrong-first-parent'
276
310
        sha1 = repo.add_inventory('wrong-first-parent', inv, ['2', '1'])
282
316
                       revision_id='wrong-first-parent')
283
317
        rev.parent_ids = ['1', '2']
284
318
        repo.add_revision('wrong-first-parent', rev)
 
319
        repo.commit_write_group()
 
320
        repo.unlock()
285
321
 
286
322
        # now setup the wrong-secondary parent case
287
323
        repo = repo_secondary
 
324
        repo.lock_write()
 
325
        repo.start_write_group()
288
326
        inv = Inventory(revision_id='wrong-secondary-parent')
289
327
        inv.root.revision = 'wrong-secondary-parent'
 
328
        if repo.supports_rich_root():
 
329
            root_id = inv.root.file_id
 
330
            repo.texts.add_lines((root_id, 'wrong-secondary-parent'), [], [])
290
331
        sha1 = repo.add_inventory('wrong-secondary-parent', inv, ['1', '3', '2'])
291
332
        rev = Revision(timestamp=0,
292
333
                       timezone=None,
296
337
                       revision_id='wrong-secondary-parent')
297
338
        rev.parent_ids = ['1', '2', '3']
298
339
        repo.add_revision('wrong-secondary-parent', rev)
 
340
        repo.commit_write_group()
 
341
        repo.unlock()
299
342
 
300
343
    def test_reconcile_wrong_order(self):
301
344
        # a wrong order in primary parents is optionally correctable
302
 
        d = bzrlib.bzrdir.BzrDir.open('wrong-first-parent')
 
345
        t = get_transport(self.get_url()).clone('wrong-first-parent')
 
346
        d = bzrlib.bzrdir.BzrDir.open_from_transport(t)
303
347
        repo = d.open_repository()
304
 
        g = repo.get_revision_graph()
305
 
        if g['wrong-first-parent'] == ['1', '2']:
306
 
            raise TestSkipped('wrong-first-parent is not setup for testing')
 
348
        repo.lock_read()
 
349
        try:
 
350
            g = repo.get_graph()
 
351
            if g.get_parent_map(['wrong-first-parent'])['wrong-first-parent'] \
 
352
                == ('1', '2'):
 
353
                raise TestSkipped('wrong-first-parent is not setup for testing')
 
354
        finally:
 
355
            repo.unlock()
307
356
        self.checkUnreconciled(d, repo.reconcile())
308
357
        # nothing should have been altered yet : inventories without
309
358
        # revisions are not data loss incurring for current format
313
362
        # and no garbage inventories
314
363
        self.assertEqual(0, reconciler.garbage_inventories)
315
364
        # and should have been fixed:
316
 
        g = repo.get_revision_graph()
317
 
        self.assertEqual(['1', '2'], g['wrong-first-parent'])
 
365
        repo.lock_read()
 
366
        self.addCleanup(repo.unlock)
 
367
        g = repo.get_graph()
 
368
        self.assertEqual(
 
369
            {'wrong-first-parent':('1', '2')},
 
370
            g.get_parent_map(['wrong-first-parent']))
318
371
 
319
 
    def test_reconcile_wrong_order_secondary(self):
320
 
        # a wrong order in secondary parents is ignored.
321
 
        d = bzrlib.bzrdir.BzrDir.open('reversed-secondary-parents')
 
372
    def test_reconcile_wrong_order_secondary_inventory(self):
 
373
        # a wrong order in the parents for inventories is ignored.
 
374
        t = get_transport(self.get_url()).clone('reversed-secondary-parents')
 
375
        d = bzrlib.bzrdir.BzrDir.open_from_transport(t)
322
376
        repo = d.open_repository()
323
377
        self.checkUnreconciled(d, repo.reconcile())
324
378
        self.checkUnreconciled(d, repo.reconcile(thorough=True))
 
379
 
 
380
 
 
381
class TestBadRevisionParents(TestCaseWithBrokenRevisionIndex):
 
382
 
 
383
    def test_aborts_if_bad_parents_in_index(self):
 
384
        """Reconcile refuses to proceed if the revision index is wrong when
 
385
        checked against the revision texts, so that it does not generate broken
 
386
        data.
 
387
 
 
388
        Ideally reconcile would fix this, but until we implement that we just
 
389
        make sure we safely detect this problem.
 
390
        """
 
391
        repo = self.make_repo_with_extra_ghost_index()
 
392
        reconciler = repo.reconcile(thorough=True)
 
393
        self.assertTrue(reconciler.aborted,
 
394
            "reconcile should have aborted due to bad parents.")
 
395
 
 
396
    def test_does_not_abort_on_clean_repo(self):
 
397
        repo = self.make_repository('.')
 
398
        reconciler = repo.reconcile(thorough=True)
 
399
        self.assertFalse(reconciler.aborted,
 
400
            "reconcile should not have aborted on an unbroken repository.")
 
401
 
 
402
 
 
403
class TestRepeatedReconcile(TestReconcile):
 
404
 
 
405
    def test_trivial_two_reconciles_no_error(self):
 
406
        tree = self.make_branch_and_tree('.')
 
407
        tree.commit('first post')
 
408
        tree.branch.repository.reconcile(thorough=True)
 
409
        tree.branch.repository.reconcile(thorough=True)