~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: Martin Pool
  • Date: 2006-03-09 03:28:52 UTC
  • mto: This revision was merged to the branch mainline in revision 1602.
  • Revision ID: mbp@sourcefrog.net-20060309032852-1097eb1947d9bceb
doc

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
 
 
19
 
 
20
 
######################################################################
21
 
# consistency checks
22
 
 
23
 
import sys
24
 
from sets import Set
25
 
 
26
 
import bzrlib
27
 
from trace import mutter
28
 
from errors import bailout
29
 
import osutils
30
 
 
31
 
def check(branch, progress=True):
32
 
    out = sys.stdout
33
 
 
34
 
    if progress:
35
 
        def p(m):
36
 
            mutter('checking ' + m)
37
 
            out.write('\rchecking: %-50.50s' % m)
38
 
            out.flush()
39
 
    else:
40
 
        def p(m):
41
 
            mutter('checking ' + m)
42
 
 
43
 
    p('history of %r' % branch.base)
44
 
    last_ptr = None
45
 
    checked_revs = Set()
46
 
    
47
 
    history = branch.revision_history()
48
 
    revno = 0
49
 
    revcount = len(history)
50
 
 
51
 
    checked_texts = {}
52
 
    
53
 
    for rid in history:
54
 
        revno += 1
55
 
        p('revision %d/%d' % (revno, revcount))
56
 
        mutter('    revision {%s}' % rid)
57
 
        rev = branch.get_revision(rid)
58
 
        if rev.revision_id != rid:
59
 
            bailout('wrong internal revision id in revision {%s}' % rid)
60
 
        if rev.precursor != last_ptr:
61
 
            bailout('mismatched precursor in revision {%s}' % rid)
62
 
        last_ptr = rid
63
 
        if rid in checked_revs:
64
 
            bailout('repeated revision {%s}' % rid)
65
 
        checked_revs.add(rid)
66
 
 
67
 
        ## TODO: Check all the required fields are present on the revision.
68
 
 
69
 
        inv = branch.get_inventory(rev.inventory_id)
70
 
        seen_ids = Set()
71
 
        seen_names = Set()
72
 
 
73
 
        p('revision %d/%d file ids' % (revno, revcount))
 
18
# TODO: Check ancestries are correct for every revision: includes
 
19
# every committed so far, and in a reasonable order.
 
20
 
 
21
# TODO: Also check non-mainline revisions mentioned as parents.
 
22
 
 
23
# TODO: Check for extra files in the control directory.
 
24
 
 
25
# TODO: Check revision, inventory and entry objects have all 
 
26
# required fields.
 
27
 
 
28
# TODO: Get every revision in the revision-store even if they're not
 
29
# referenced by history and make sure they're all valid.
 
30
 
 
31
import bzrlib.ui
 
32
from bzrlib.trace import note, warning
 
33
from bzrlib.osutils import rename, sha_string, fingerprint_file
 
34
from bzrlib.trace import mutter
 
35
from bzrlib.errors import BzrCheckError, NoSuchRevision
 
36
from bzrlib.inventory import ROOT_ID
 
37
 
 
38
 
 
39
class Check(object):
 
40
    """Check a branch"""
 
41
 
 
42
    def __init__(self, branch):
 
43
        self.branch = branch
 
44
        self.repository = branch.repository
 
45
        self.checked_text_cnt = 0
 
46
        self.checked_rev_cnt = 0
 
47
        self.ghosts = []
 
48
        self.repeated_text_cnt = 0
 
49
        self.missing_parent_links = {}
 
50
        self.missing_inventory_sha_cnt = 0
 
51
        self.missing_revision_cnt = 0
 
52
        # maps (file-id, version) -> sha1
 
53
        self.checked_texts = {}
 
54
        self.checked_weaves = {}
 
55
 
 
56
    def check(self):
 
57
        self.branch.lock_read()
 
58
        self.progress = bzrlib.ui.ui_factory.progress_bar()
 
59
        try:
 
60
            self.progress.update('retrieving inventory', 0, 0)
 
61
            # do not put in init, as it should be done with progess,
 
62
            # and inside the lock.
 
63
            self.inventory_weave = self.branch.repository.get_inventory_weave()
 
64
            self.history = self.branch.revision_history()
 
65
            if not len(self.history):
 
66
                # nothing to see here
 
67
                return
 
68
            self.plan_revisions()
 
69
            revno = 0
 
70
            self.check_weaves()
 
71
            while revno < len(self.planned_revisions):
 
72
                rev_id = self.planned_revisions[revno]
 
73
                self.progress.update('checking revision', revno,
 
74
                                     len(self.planned_revisions))
 
75
                revno += 1
 
76
                self.check_one_rev(rev_id)
 
77
        finally:
 
78
            self.progress.clear()
 
79
            self.branch.unlock()
 
80
 
 
81
    def plan_revisions(self):
 
82
        repository = self.branch.repository
 
83
        if not repository.revision_store.listable():
 
84
            self.planned_revisions = repository.get_ancestry(self.history[-1])
 
85
            self.planned_revisions.remove(None)
 
86
            # FIXME progress bars should support this more nicely.
 
87
            self.progress.clear()
 
88
            print ("Checking reachable history -"
 
89
                   " for a complete check use a local branch.")
 
90
            return
 
91
        
 
92
        self.planned_revisions = set(repository.revision_store)
 
93
        inventoried = set(self.inventory_weave.names())
 
94
        awol = self.planned_revisions - inventoried
 
95
        if len(awol) > 0:
 
96
            raise BzrCheckError('Stored revisions missing from inventory'
 
97
                '{%s}' % ','.join([f for f in awol]))
 
98
        self.planned_revisions = list(self.planned_revisions)
 
99
 
 
100
    def report_results(self, verbose):
 
101
        note('checked branch %s format %s',
 
102
             self.branch.base, 
 
103
             self.branch._format)
 
104
 
 
105
        note('%6d revisions', self.checked_rev_cnt)
 
106
        note('%6d unique file texts', self.checked_text_cnt)
 
107
        note('%6d repeated file texts', self.repeated_text_cnt)
 
108
        note('%6d weaves', len(self.checked_weaves))
 
109
        if self.missing_inventory_sha_cnt:
 
110
            note('%6d revisions are missing inventory_sha1',
 
111
                 self.missing_inventory_sha_cnt)
 
112
        if self.missing_revision_cnt:
 
113
            note('%6d revisions are mentioned but not present',
 
114
                 self.missing_revision_cnt)
 
115
        if len(self.ghosts):
 
116
            note('%6d ghost revisions', len(self.ghosts))
 
117
            if verbose:
 
118
                for ghost in self.ghosts:
 
119
                    note('      %s', ghost)
 
120
        if len(self.missing_parent_links):
 
121
            note('%6d revisions missing parents in ancestry', 
 
122
                 len(self.missing_parent_links))
 
123
            if verbose:
 
124
                for link, linkers in self.missing_parent_links.items():
 
125
                    note('      %s should be in the ancestry for:', link)
 
126
                    for linker in linkers:
 
127
                        note('       * %s', linker)
 
128
 
 
129
    def check_one_rev(self, rev_id):
 
130
        """Check one revision.
 
131
 
 
132
        rev_id - the one to check
 
133
 
 
134
        last_rev_id - the previous one on the mainline, if any.
 
135
        """
 
136
 
 
137
        # mutter('    revision {%s}', rev_id)
 
138
        branch = self.branch
 
139
        try:
 
140
            rev_history_position = self.history.index(rev_id)
 
141
        except ValueError:
 
142
            rev_history_position = None
 
143
        last_rev_id = None
 
144
        if rev_history_position:
 
145
            rev = branch.repository.get_revision(rev_id)
 
146
            if rev_history_position > 0:
 
147
                last_rev_id = self.history[rev_history_position - 1]
 
148
        else:
 
149
            rev = branch.repository.get_revision(rev_id)
 
150
                
 
151
        if rev.revision_id != rev_id:
 
152
            raise BzrCheckError('wrong internal revision id in revision {%s}'
 
153
                                % rev_id)
 
154
 
 
155
        # check the previous history entry is a parent of this entry
 
156
        if rev.parent_ids:
 
157
            if last_rev_id is not None:
 
158
                for parent_id in rev.parent_ids:
 
159
                    if parent_id == last_rev_id:
 
160
                        break
 
161
                else:
 
162
                    raise BzrCheckError("previous revision {%s} not listed among "
 
163
                                        "parents of {%s}"
 
164
                                        % (last_rev_id, rev_id))
 
165
            for parent in rev.parent_ids:
 
166
                if not parent in self.planned_revisions:
 
167
                    missing_links = self.missing_parent_links.get(parent, [])
 
168
                    missing_links.append(rev_id)
 
169
                    self.missing_parent_links[parent] = missing_links
 
170
                    # list based so somewhat slow,
 
171
                    # TODO have a planned_revisions list and set.
 
172
                    if self.branch.repository.has_revision(parent):
 
173
                        missing_ancestry = self.repository.get_ancestry(parent)
 
174
                        for missing in missing_ancestry:
 
175
                            if (missing is not None 
 
176
                                and missing not in self.planned_revisions):
 
177
                                self.planned_revisions.append(missing)
 
178
                    else:
 
179
                        self.ghosts.append(rev_id)
 
180
        elif last_rev_id:
 
181
            raise BzrCheckError("revision {%s} has no parents listed "
 
182
                                "but preceded by {%s}"
 
183
                                % (rev_id, last_rev_id))
 
184
 
 
185
        if rev.inventory_sha1:
 
186
            inv_sha1 = branch.repository.get_inventory_sha1(rev_id)
 
187
            if inv_sha1 != rev.inventory_sha1:
 
188
                raise BzrCheckError('Inventory sha1 hash doesn\'t match'
 
189
                    ' value in revision {%s}' % rev_id)
 
190
        else:
 
191
            missing_inventory_sha_cnt += 1
 
192
            mutter("no inventory_sha1 on revision {%s}", rev_id)
 
193
        self._check_revision_tree(rev_id)
 
194
        self.checked_rev_cnt += 1
 
195
 
 
196
    def check_weaves(self):
 
197
        """Check all the weaves we can get our hands on.
 
198
        """
 
199
        n_weaves = 1
 
200
        weave_ids = []
 
201
        if self.branch.repository.weave_store.listable():
 
202
            weave_ids = list(self.branch.repository.weave_store)
 
203
            n_weaves = len(weave_ids)
 
204
        self.progress.update('checking weave', 0, n_weaves)
 
205
        self.inventory_weave.check(progress_bar=self.progress)
 
206
        for i, weave_id in enumerate(weave_ids):
 
207
            self.progress.update('checking weave', i, n_weaves)
 
208
            w = self.branch.repository.weave_store.get_weave(weave_id,
 
209
                    self.branch.repository.get_transaction())
 
210
            # No progress here, because it looks ugly.
 
211
            w.check()
 
212
            self.checked_weaves[weave_id] = True
 
213
 
 
214
    def _check_revision_tree(self, rev_id):
 
215
        tree = self.branch.repository.revision_tree(rev_id)
 
216
        inv = tree.inventory
 
217
        seen_ids = {}
74
218
        for file_id in inv:
75
219
            if file_id in seen_ids:
76
 
                bailout('duplicated file_id {%s} in inventory for revision {%s}'
77
 
                        % (file_id, revid))
78
 
            seen_ids.add(file_id)
79
 
 
80
 
        i = 0
81
 
        len_inv = len(inv)
 
220
                raise BzrCheckError('duplicated file_id {%s} '
 
221
                                    'in inventory for revision {%s}'
 
222
                                    % (file_id, rev_id))
 
223
            seen_ids[file_id] = True
82
224
        for file_id in inv:
83
 
            i += 1
84
 
            if (i % 100) == 0:
85
 
                p('revision %d/%d file text %d/%d' % (revno, revcount, i, len_inv))
86
 
 
87
225
            ie = inv[file_id]
88
 
 
89
 
            if ie.parent_id != None:
90
 
                if ie.parent_id not in seen_ids:
91
 
                    bailout('missing parent {%s} in inventory for revision {%s}'
92
 
                            % (ie.parent_id, revid))
93
 
 
94
 
            if ie.kind == 'file':
95
 
                if ie.text_id in checked_texts:
96
 
                    fp = checked_texts[ie.text_id]
97
 
                else:
98
 
                    if not ie.text_id in branch.text_store:
99
 
                        bailout('text {%s} not in text_store' % ie.text_id)
100
 
 
101
 
                    tf = branch.text_store[ie.text_id]
102
 
                    fp = osutils.fingerprint_file(tf)
103
 
                    checked_texts[ie.text_id] = fp
104
 
 
105
 
                if ie.text_size != fp['size']:
106
 
                    bailout('text {%s} wrong size' % ie.text_id)
107
 
                if ie.text_sha1 != fp['sha1']:
108
 
                    bailout('text {%s} wrong sha1' % ie.text_id)
109
 
            elif ie.kind == 'directory':
110
 
                if ie.text_sha1 != None or ie.text_size != None or ie.text_id != None:
111
 
                    bailout('directory {%s} has text in revision {%s}'
112
 
                            % (file_id, revid))
113
 
 
114
 
        p('revision %d/%d file paths' % (revno, revcount))
 
226
            ie.check(self, rev_id, inv, tree)
 
227
        seen_names = {}
115
228
        for path, ie in inv.iter_entries():
116
229
            if path in seen_names:
117
 
                bailout('duplicated path %r in inventory for revision {%s}' % (path, revid))
118
 
            seen_names.add(path)
119
 
 
120
 
 
121
 
    p('done')
122
 
    if progress:
123
 
        print 
124
 
 
125
 
 
 
230
                raise BzrCheckError('duplicated path %s '
 
231
                                    'in inventory for revision {%s}'
 
232
                                    % (path, rev_id))
 
233
            seen_names[path] = True
 
234
 
 
235
 
 
236
def check(branch, verbose):
 
237
    """Run consistency checks on a branch."""
 
238
    checker = Check(branch)
 
239
    checker.check()
 
240
    checker.report_results(verbose)