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
30
class TestsNeedingReweave(TestCaseWithRepository):
32
class TestReconcile(TestCaseWithRepository):
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)
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,
48
repo.get_transaction())
51
class TestsNeedingReweave(TestReconcile):
33
54
super(TestsNeedingReweave, self).setUp()
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, [])
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)
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,
93
repo.get_transaction())
95
def test_reweave_inventory_without_revision_reconcile(self):
116
self.checkNoBackupInventory(d)
118
def test_reconile_empty(self):
119
# in an empty repo, theres nothing to do.
120
self.checkEmptyReconcile()
122
def test_reconcile_empty_thorough(self):
123
# reconcile should accept thorough=True
124
self.checkEmptyReconcile(thorough=True)
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')
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')
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)
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)
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)
146
def check_thorough_reweave_missing_revision(self, aBzrDir, reconcile,
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
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'))
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')
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')
183
reconciler = Reconciler(d)
184
reconciler.reconcile()
186
self.check_thorough_reweave_missing_revision(d, reconcile)
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,
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)
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'))
234
class TestReconcileWithIncorrectRevisionCache(TestReconcile):
235
"""Ancestry data gets cached in knits and weaves should be reconcilable.
237
This class tests that reconcile can correct invalid caches (such as after
242
super(TestReconcileWithIncorrectRevisionCache, self).setUp()
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.
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()
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,
272
committer="Foo Bar <foo@example.com>",
275
revision_id='wrong-first-parent')
276
rev.parent_ids = ['1', '2']
277
repo.add_revision('wrong-first-parent', rev)
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,
285
committer="Foo Bar <foo@example.com>",
288
revision_id='wrong-secondary-parent')
289
rev.parent_ids = ['1', '2', '3']
290
repo.add_revision('wrong-secondary-parent', rev)
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'])
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))