/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/check.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-07-20 08:56:45 UTC
  • mfrom: (4526.9.23 apply-inventory-delta)
  • Revision ID: pqm@pqm.ubuntu.com-20090720085645-54mtgybxua0yx6hw
(robertc) Add checks for inventory deltas which try to ensure that
        deltas that are not an exact fit are not applied. (Robert
        Collins, bug 397705, bug 367633)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
# TODO: Check ancestries are correct for every revision: includes
 
18
# every committed so far, and in a reasonable order.
 
19
 
 
20
# TODO: Also check non-mainline revisions mentioned as parents.
 
21
 
 
22
# TODO: Check for extra files in the control directory.
 
23
 
 
24
# TODO: Check revision, inventory and entry objects have all
 
25
# required fields.
 
26
 
 
27
# TODO: Get every revision in the revision-store even if they're not
 
28
# referenced by history and make sure they're all valid.
 
29
 
 
30
# TODO: Perhaps have a way to record errors other than by raising exceptions;
 
31
# would perhaps be enough to accumulate exception objects in a list without
 
32
# raising them.  If there's more than one exception it'd be good to see them
 
33
# all.
 
34
 
 
35
from bzrlib import errors, osutils
 
36
from bzrlib import repository as _mod_repository
 
37
from bzrlib import revision
 
38
from bzrlib.branch import Branch
 
39
from bzrlib.bzrdir import BzrDir
 
40
from bzrlib.errors import BzrCheckError
 
41
from bzrlib.repository import Repository
 
42
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
 
43
from bzrlib.trace import log_error, note
 
44
import bzrlib.ui
 
45
from bzrlib.workingtree import WorkingTree
 
46
 
 
47
class Check(object):
 
48
    """Check a repository"""
 
49
 
 
50
    # The Check object interacts with InventoryEntry.check, etc.
 
51
 
 
52
    def __init__(self, repository):
 
53
        self.repository = repository
 
54
        self.checked_text_cnt = 0
 
55
        self.checked_rev_cnt = 0
 
56
        self.ghosts = []
 
57
        self.repeated_text_cnt = 0
 
58
        self.missing_parent_links = {}
 
59
        self.missing_inventory_sha_cnt = 0
 
60
        self.missing_revision_cnt = 0
 
61
        # maps (file-id, version) -> sha1; used by InventoryFile._check
 
62
        self.checked_texts = {}
 
63
        self.checked_weaves = set()
 
64
        self.unreferenced_versions = set()
 
65
        self.inconsistent_parents = []
 
66
        self.rich_roots = repository.supports_rich_root()
 
67
        self.text_key_references = {}
 
68
 
 
69
    def check(self):
 
70
        self.repository.lock_read()
 
71
        self.progress = bzrlib.ui.ui_factory.nested_progress_bar()
 
72
        try:
 
73
            self.progress.update('retrieving inventory', 0, 2)
 
74
            # do not put in init, as it should be done with progess,
 
75
            # and inside the lock.
 
76
            self.inventory_weave = self.repository.inventories
 
77
            self.progress.update('checking revision graph', 1)
 
78
            self.check_revision_graph()
 
79
            self.plan_revisions()
 
80
            revno = 0
 
81
            while revno < len(self.planned_revisions):
 
82
                rev_id = self.planned_revisions[revno]
 
83
                self.progress.update('checking revision', revno,
 
84
                                     len(self.planned_revisions))
 
85
                revno += 1
 
86
                self.check_one_rev(rev_id)
 
87
            # check_weaves is done after the revision scan so that
 
88
            # revision index is known to be valid.
 
89
            self.check_weaves()
 
90
        finally:
 
91
            self.progress.finished()
 
92
            self.repository.unlock()
 
93
 
 
94
    def check_revision_graph(self):
 
95
        if not self.repository.revision_graph_can_have_wrong_parents():
 
96
            # This check is not necessary.
 
97
            self.revs_with_bad_parents_in_index = None
 
98
            return
 
99
        bad_revisions = self.repository._find_inconsistent_revision_parents()
 
100
        self.revs_with_bad_parents_in_index = list(bad_revisions)
 
101
 
 
102
    def plan_revisions(self):
 
103
        repository = self.repository
 
104
        self.planned_revisions = repository.all_revision_ids()
 
105
        self.progress.clear()
 
106
        inventoried = set(key[-1] for key in self.inventory_weave.keys())
 
107
        awol = set(self.planned_revisions) - inventoried
 
108
        if len(awol) > 0:
 
109
            raise BzrCheckError('Stored revisions missing from inventory'
 
110
                '{%s}' % ','.join([f for f in awol]))
 
111
 
 
112
    def report_results(self, verbose):
 
113
        note('checked repository %s format %s',
 
114
             self.repository.bzrdir.root_transport,
 
115
             self.repository._format)
 
116
        note('%6d revisions', self.checked_rev_cnt)
 
117
        note('%6d file-ids', len(self.checked_weaves))
 
118
        note('%6d unique file texts', self.checked_text_cnt)
 
119
        note('%6d repeated file texts', self.repeated_text_cnt)
 
120
        note('%6d unreferenced text versions',
 
121
             len(self.unreferenced_versions))
 
122
        if self.missing_inventory_sha_cnt:
 
123
            note('%6d revisions are missing inventory_sha1',
 
124
                 self.missing_inventory_sha_cnt)
 
125
        if self.missing_revision_cnt:
 
126
            note('%6d revisions are mentioned but not present',
 
127
                 self.missing_revision_cnt)
 
128
        if len(self.ghosts):
 
129
            note('%6d ghost revisions', len(self.ghosts))
 
130
            if verbose:
 
131
                for ghost in self.ghosts:
 
132
                    note('      %s', ghost)
 
133
        if len(self.missing_parent_links):
 
134
            note('%6d revisions missing parents in ancestry',
 
135
                 len(self.missing_parent_links))
 
136
            if verbose:
 
137
                for link, linkers in self.missing_parent_links.items():
 
138
                    note('      %s should be in the ancestry for:', link)
 
139
                    for linker in linkers:
 
140
                        note('       * %s', linker)
 
141
            if verbose:
 
142
                for file_id, revision_id in self.unreferenced_versions:
 
143
                    log_error('unreferenced version: {%s} in %s', revision_id,
 
144
                        file_id)
 
145
        if len(self.inconsistent_parents):
 
146
            note('%6d inconsistent parents', len(self.inconsistent_parents))
 
147
            if verbose:
 
148
                for info in self.inconsistent_parents:
 
149
                    revision_id, file_id, found_parents, correct_parents = info
 
150
                    note('      * %s version %s has parents %r '
 
151
                         'but should have %r'
 
152
                         % (file_id, revision_id, found_parents,
 
153
                             correct_parents))
 
154
        if self.revs_with_bad_parents_in_index:
 
155
            note('%6d revisions have incorrect parents in the revision index',
 
156
                 len(self.revs_with_bad_parents_in_index))
 
157
            if verbose:
 
158
                for item in self.revs_with_bad_parents_in_index:
 
159
                    revision_id, index_parents, actual_parents = item
 
160
                    note(
 
161
                        '       %s has wrong parents in index: '
 
162
                        '%r should be %r',
 
163
                        revision_id, index_parents, actual_parents)
 
164
 
 
165
    def check_one_rev(self, rev_id):
 
166
        """Check one revision.
 
167
 
 
168
        rev_id - the one to check
 
169
        """
 
170
        rev = self.repository.get_revision(rev_id)
 
171
 
 
172
        if rev.revision_id != rev_id:
 
173
            raise BzrCheckError('wrong internal revision id in revision {%s}'
 
174
                                % rev_id)
 
175
 
 
176
        for parent in rev.parent_ids:
 
177
            if not parent in self.planned_revisions:
 
178
                # rev has a parent we didn't know about.
 
179
                missing_links = self.missing_parent_links.get(parent, [])
 
180
                missing_links.append(rev_id)
 
181
                self.missing_parent_links[parent] = missing_links
 
182
                # list based so somewhat slow,
 
183
                # TODO have a planned_revisions list and set.
 
184
                if self.repository.has_revision(parent):
 
185
                    missing_ancestry = self.repository.get_ancestry(parent)
 
186
                    for missing in missing_ancestry:
 
187
                        if (missing is not None
 
188
                            and missing not in self.planned_revisions):
 
189
                            self.planned_revisions.append(missing)
 
190
                else:
 
191
                    self.ghosts.append(rev_id)
 
192
 
 
193
        if rev.inventory_sha1:
 
194
            # Loopback - this is currently circular logic as the
 
195
            # knit get_inventory_sha1 call returns rev.inventory_sha1.
 
196
            # Repository.py's get_inventory_sha1 should instead return
 
197
            # inventories.get_record_stream([(revid,)]).next().sha1 or
 
198
            # similar.
 
199
            inv_sha1 = self.repository.get_inventory_sha1(rev_id)
 
200
            if inv_sha1 != rev.inventory_sha1:
 
201
                raise BzrCheckError('Inventory sha1 hash doesn\'t match'
 
202
                    ' value in revision {%s}' % rev_id)
 
203
        self._check_revision_tree(rev_id)
 
204
        self.checked_rev_cnt += 1
 
205
 
 
206
    def check_weaves(self):
 
207
        """Check all the weaves we can get our hands on.
 
208
        """
 
209
        weave_ids = []
 
210
        self.progress.update('checking inventory', 0, 2)
 
211
        self.inventory_weave.check(progress_bar=self.progress)
 
212
        self.progress.update('checking text storage', 1, 2)
 
213
        self.repository.texts.check(progress_bar=self.progress)
 
214
        weave_checker = self.repository._get_versioned_file_checker(
 
215
            text_key_references=self.text_key_references)
 
216
        result = weave_checker.check_file_version_parents(
 
217
            self.repository.texts, progress_bar=self.progress)
 
218
        self.checked_weaves = weave_checker.file_ids
 
219
        bad_parents, unused_versions = result
 
220
        bad_parents = bad_parents.items()
 
221
        for text_key, (stored_parents, correct_parents) in bad_parents:
 
222
            # XXX not ready for id join/split operations.
 
223
            weave_id = text_key[0]
 
224
            revision_id = text_key[-1]
 
225
            weave_parents = tuple([parent[-1] for parent in stored_parents])
 
226
            correct_parents = tuple([parent[-1] for parent in correct_parents])
 
227
            self.inconsistent_parents.append(
 
228
                (revision_id, weave_id, weave_parents, correct_parents))
 
229
        self.unreferenced_versions.update(unused_versions)
 
230
 
 
231
    def _check_revision_tree(self, rev_id):
 
232
        tree = self.repository.revision_tree(rev_id)
 
233
        inv = tree.inventory
 
234
        seen_ids = set()
 
235
        seen_names = set()
 
236
        for path, ie in inv.iter_entries():
 
237
            self._add_entry_to_text_key_references(inv, ie)
 
238
            file_id = ie.file_id
 
239
            if file_id in seen_ids:
 
240
                raise BzrCheckError('duplicated file_id {%s} '
 
241
                                    'in inventory for revision {%s}'
 
242
                                    % (file_id, rev_id))
 
243
            seen_ids.add(file_id)
 
244
            ie.check(self, rev_id, inv, tree)
 
245
            if path in seen_names:
 
246
                raise BzrCheckError('duplicated path %s '
 
247
                                    'in inventory for revision {%s}'
 
248
                                    % (path, rev_id))
 
249
            seen_names.add(path)
 
250
 
 
251
    def _add_entry_to_text_key_references(self, inv, entry):
 
252
        if not self.rich_roots and entry == inv.root:
 
253
            return
 
254
        key = (entry.file_id, entry.revision)
 
255
        self.text_key_references.setdefault(key, False)
 
256
        if entry.revision == inv.revision_id:
 
257
            self.text_key_references[key] = True
 
258
 
 
259
 
 
260
@deprecated_function(deprecated_in((1,6,0)))
 
261
def check(branch, verbose):
 
262
    """Run consistency checks on a branch.
 
263
 
 
264
    Results are reported through logging.
 
265
 
 
266
    Deprecated in 1.6.  Please use check_branch instead.
 
267
 
 
268
    :raise BzrCheckError: if there's a consistency error.
 
269
    """
 
270
    check_branch(branch, verbose)
 
271
 
 
272
 
 
273
def check_branch(branch, verbose):
 
274
    """Run consistency checks on a branch.
 
275
 
 
276
    Results are reported through logging.
 
277
 
 
278
    :raise BzrCheckError: if there's a consistency error.
 
279
    """
 
280
    branch.lock_read()
 
281
    try:
 
282
        branch_result = branch.check()
 
283
    finally:
 
284
        branch.unlock()
 
285
    branch_result.report_results(verbose)
 
286
 
 
287
 
 
288
def check_dwim(path, verbose, do_branch=False, do_repo=False, do_tree=False):
 
289
    try:
 
290
        tree, branch, repo, relpath = \
 
291
                        BzrDir.open_containing_tree_branch_or_repository(path)
 
292
    except errors.NotBranchError:
 
293
        tree = branch = repo = None
 
294
 
 
295
    if do_tree:
 
296
        if tree is not None:
 
297
            note("Checking working tree at '%s'."
 
298
                 % (tree.bzrdir.root_transport.base,))
 
299
            tree._check()
 
300
        else:
 
301
            log_error("No working tree found at specified location.")
 
302
 
 
303
    if branch is not None:
 
304
        # We have a branch
 
305
        if repo is None:
 
306
            # The branch is in a shared repository
 
307
            repo = branch.repository
 
308
        branches = [branch]
 
309
    elif repo is not None:
 
310
        branches = repo.find_branches(using=True)
 
311
 
 
312
    if repo is not None:
 
313
        repo.lock_read()
 
314
        try:
 
315
            if do_repo:
 
316
                note("Checking repository at '%s'."
 
317
                     % (repo.bzrdir.root_transport.base,))
 
318
                result = repo.check()
 
319
                result.report_results(verbose)
 
320
            if do_branch:
 
321
                if branches == []:
 
322
                    log_error("No branch found at specified location.")
 
323
                else:
 
324
                    for branch in branches:
 
325
                        note("Checking branch at '%s'."
 
326
                             % (branch.bzrdir.root_transport.base,))
 
327
                        check_branch(branch, verbose)
 
328
        finally:
 
329
            repo.unlock()
 
330
    else:
 
331
        if do_branch:
 
332
            log_error("No branch found at specified location.")
 
333
        if do_repo:
 
334
            log_error("No repository found at specified location.")