~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: Robert Collins
  • Date: 2005-10-16 07:11:29 UTC
  • mto: This revision was merged to the branch mainline in revision 1459.
  • Revision ID: robertc@lifelesslap.robertcollins.net-20051016071129-d0cd8a663a42cea0
teach TransportStore.add to accept an optional file suffix, which does not alter the fileid.

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
 
 
29
import bzrlib.ui
 
30
from bzrlib.trace import note, warning
 
31
from bzrlib.osutils import rename, sha_string, fingerprint_file
 
32
from bzrlib.trace import mutter
 
33
from bzrlib.errors import BzrCheckError, NoSuchRevision
 
34
from bzrlib.inventory import ROOT_ID
 
35
from bzrlib.branch import gen_root_id
 
36
 
 
37
 
 
38
class Check(object):
 
39
    """Check a branch"""
 
40
 
 
41
    def __init__(self, branch):
 
42
        self.branch = branch
 
43
        self.checked_text_cnt = 0
 
44
        self.checked_rev_cnt = 0
 
45
        self.ghosts = []
 
46
        self.repeated_text_cnt = 0
 
47
        self.missing_parent_links = {}
 
48
        self.missing_inventory_sha_cnt = 0
 
49
        self.missing_revision_cnt = 0
 
50
        # maps (file-id, version) -> sha1
 
51
        self.checked_texts = {}
 
52
 
 
53
    def check(self):
 
54
        self.branch.lock_read()
 
55
        try:
 
56
            self.history = self.branch.revision_history()
 
57
            if not len(self.history):
 
58
                # nothing to see here
 
59
                return
 
60
            self.planned_revisions = self.branch.get_ancestry(self.history[-1])
 
61
            self.planned_revisions.remove(None)
 
62
            revno = 0
 
63
    
 
64
            self.progress = bzrlib.ui.ui_factory.progress_bar()
 
65
            while revno < len(self.planned_revisions):
 
66
                rev_id = self.planned_revisions[revno]
 
67
                self.progress.update('checking revision', revno,
 
68
                                     len(self.planned_revisions))
 
69
                revno += 1
 
70
                self.check_one_rev(rev_id)
 
71
            self.progress.clear()
 
72
        finally:
 
73
            self.branch.unlock()
 
74
 
 
75
    def report_results(self, verbose):
 
76
        note('checked branch %s format %d',
 
77
             self.branch.base, 
 
78
             self.branch._branch_format)
 
79
 
 
80
        note('%6d revisions', self.checked_rev_cnt)
 
81
        note('%6d unique file texts', self.checked_text_cnt)
 
82
        note('%6d repeated file texts', self.repeated_text_cnt)
 
83
        if self.missing_inventory_sha_cnt:
 
84
            note('%6d revisions are missing inventory_sha1',
 
85
                 self.missing_inventory_sha_cnt)
 
86
        if self.missing_revision_cnt:
 
87
            note('%6d revisions are mentioned but not present',
 
88
                 self.missing_revision_cnt)
 
89
        if len(self.ghosts):
 
90
            note('%6d ghost revisions', len(self.ghosts))
 
91
            if verbose:
 
92
                for ghost in self.ghosts:
 
93
                    note('      %s', ghost)
 
94
        if len(self.missing_parent_links):
 
95
            note('%6d revisions missing parents in ancestry', 
 
96
                 len(self.missing_parent_links))
 
97
            if verbose:
 
98
                for link, linkers in self.missing_parent_links.items():
 
99
                    note('      %s should be in the ancestry for:', link)
 
100
                    for linker in linkers:
 
101
                        note('       * %s', linker)
 
102
 
 
103
    def check_one_rev(self, rev_id):
 
104
        """Check one revision.
 
105
 
 
106
        rev_id - the one to check
 
107
 
 
108
        last_rev_id - the previous one on the mainline, if any.
 
109
        """
 
110
 
 
111
        # mutter('    revision {%s}' % rev_id)
 
112
        branch = self.branch
 
113
        try:
 
114
            rev_history_position = self.history.index(rev_id)
 
115
        except ValueError:
 
116
            rev_history_position = None
 
117
        last_rev_id = None
 
118
        if rev_history_position:
 
119
            rev = branch.get_revision(rev_id)
 
120
            if rev_history_position > 0:
 
121
                last_rev_id = self.history[rev_history_position - 1]
 
122
        else:
 
123
            rev = branch.get_revision(rev_id)
 
124
                
 
125
        if rev.revision_id != rev_id:
 
126
            raise BzrCheckError('wrong internal revision id in revision {%s}'
 
127
                                % rev_id)
 
128
 
 
129
        # check the previous history entry is a parent of this entry
 
130
        if rev.parent_ids:
 
131
            if last_rev_id is None and rev_history_position is not None:
 
132
                # what if the start is a ghost ? i.e. conceptually the 
 
133
                # baz imports
 
134
                raise BzrCheckError("revision {%s} has %d parents, but is the "
 
135
                                    "start of the branch"
 
136
                                    % (rev_id, len(rev.parent_ids)))
 
137
            if last_rev_id is not None:
 
138
                for parent_id in rev.parent_ids:
 
139
                    if parent_id == last_rev_id:
 
140
                        break
 
141
                else:
 
142
                    raise BzrCheckError("previous revision {%s} not listed among "
 
143
                                        "parents of {%s}"
 
144
                                        % (last_rev_id, rev_id))
 
145
            for parent in rev.parent_ids:
 
146
                if not parent in self.planned_revisions:
 
147
                    missing_links = self.missing_parent_links.get(parent, [])
 
148
                    missing_links.append(rev_id)
 
149
                    self.missing_parent_links[parent] = missing_links
 
150
                    # list based so slow, TODO have a planned_revisions list and set.
 
151
                    if self.branch.has_revision(parent):
 
152
                        missing_ancestry = self.branch.get_ancestry(parent)
 
153
                        for missing in missing_ancestry:
 
154
                            if (missing is not None 
 
155
                                and missing not in self.planned_revisions):
 
156
                                self.planned_revisions.append(missing)
 
157
                    else:
 
158
                        self.ghosts.append(rev_id)
 
159
        elif last_rev_id:
 
160
            raise BzrCheckError("revision {%s} has no parents listed "
 
161
                                "but preceded by {%s}"
 
162
                                % (rev_id, last_rev_id))
 
163
 
 
164
        if rev.inventory_sha1:
 
165
            inv_sha1 = branch.get_inventory_sha1(rev_id)
 
166
            if inv_sha1 != rev.inventory_sha1:
 
167
                raise BzrCheckError('Inventory sha1 hash doesn\'t match'
 
168
                    ' value in revision {%s}' % rev_id)
 
169
        else:
 
170
            missing_inventory_sha_cnt += 1
 
171
            mutter("no inventory_sha1 on revision {%s}" % rev_id)
 
172
        self._check_revision_tree(rev_id)
 
173
        self.checked_rev_cnt += 1
 
174
 
 
175
    def _check_revision_tree(self, rev_id):
 
176
        tree = self.branch.revision_tree(rev_id)
 
177
        inv = tree.inventory
 
178
        seen_ids = {}
74
179
        for file_id in inv:
75
180
            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)
 
181
                raise BzrCheckError('duplicated file_id {%s} '
 
182
                                    'in inventory for revision {%s}'
 
183
                                    % (file_id, rev_id))
 
184
            seen_ids[file_id] = True
82
185
        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
186
            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))
 
187
            ie.check(self, rev_id, inv, tree)
 
188
        seen_names = {}
115
189
        for path, ie in inv.iter_entries():
116
190
            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
 
 
 
191
                raise BzrCheckError('duplicated path %s '
 
192
                                    'in inventory for revision {%s}'
 
193
                                    % (path, rev_id))
 
194
            seen_names[path] = True
 
195
 
 
196
 
 
197
def check(branch, verbose):
 
198
    """Run consistency checks on a branch."""
 
199
    checker = Check(branch)
 
200
    checker.check()
 
201
    checker.report_results(verbose)