~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: Martin Pool
  • Date: 2005-06-17 07:34:04 UTC
  • Revision ID: mbp@sourcefrog.net-20050617073404-7c914dc1bd15f38a
- don't display progress bars on really dumb terminals

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
 
19
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()
 
20
 
 
21
def check(branch, update=False):
 
22
    """Run consistency checks on a branch.
 
23
 
 
24
    If update is True, for revisions missing certain information
 
25
    (right now this is inventory_sha1 and revision_sha1),
 
26
    update them to include the appropriate values.
 
27
    """
 
28
    import sys
 
29
 
 
30
    from bzrlib.trace import mutter
 
31
    from bzrlib.errors import BzrCheckError
 
32
    from bzrlib.osutils import fingerprint_file
 
33
    from bzrlib.progress import ProgressBar
 
34
 
 
35
    if update:
 
36
        branch.lock_write()
39
37
    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))
74
 
        for file_id in inv:
75
 
            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)
82
 
        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
 
            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))
115
 
        for path, ie in inv.iter_entries():
116
 
            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
 
 
 
38
        branch.lock_read()
 
39
 
 
40
    try:
 
41
 
 
42
        out = sys.stdout
 
43
 
 
44
        pb = ProgressBar(show_spinner=True)
 
45
        last_ptr = None
 
46
        checked_revs = {}
 
47
 
 
48
        missing_inventory_sha_cnt = 0
 
49
 
 
50
        history = branch.revision_history()
 
51
        revno = 0
 
52
        revcount = len(history)
 
53
 
 
54
        checked_texts = {}
 
55
 
 
56
        updated_revisions = []
 
57
 
 
58
        # Set to True in the case that the previous revision entry
 
59
        # was updated, since this will affect future revision entries
 
60
        updated_previous_revision = False
 
61
 
 
62
        for rid in history:
 
63
            revno += 1
 
64
            pb.update('checking revision', revno, revcount)
 
65
            mutter('    revision {%s}' % rid)
 
66
            rev = branch.get_revision(rid)
 
67
            if rev.revision_id != rid:
 
68
                raise BzrCheckError('wrong internal revision id in revision {%s}' % rid)
 
69
            if rev.precursor != last_ptr:
 
70
                raise BzrCheckError('mismatched precursor in revision {%s}' % rid)
 
71
            last_ptr = rid
 
72
            if rid in checked_revs:
 
73
                raise BzrCheckError('repeated revision {%s}' % rid)
 
74
            checked_revs[rid] = True
 
75
 
 
76
            ## TODO: Check all the required fields are present on the revision.
 
77
 
 
78
            updated = False
 
79
            if rev.inventory_sha1:
 
80
                #mutter('    checking inventory hash {%s}' % rev.inventory_sha1)
 
81
                inv_sha1 = branch.get_inventory_sha1(rev.inventory_id)
 
82
                if inv_sha1 != rev.inventory_sha1:
 
83
                    raise BzrCheckError('Inventory sha1 hash doesn\'t match'
 
84
                        ' value in revision {%s}' % rid)
 
85
            elif update:
 
86
                inv_sha1 = branch.get_inventory_sha1(rev.inventory_id)
 
87
                rev.inventory_sha1 = inv_sha1
 
88
                updated = True
 
89
            else:
 
90
                missing_inventory_sha_cnt += 1
 
91
                mutter("no inventory_sha1 on revision {%s}" % rid)
 
92
 
 
93
            if rev.precursor:
 
94
                if rev.precursor_sha1:
 
95
                    precursor_sha1 = branch.get_revision_sha1(rev.precursor)
 
96
                    if updated_previous_revision: 
 
97
                        # we don't expect the hashes to match, because
 
98
                        # we had to modify the previous revision_history entry.
 
99
                        rev.precursor_sha1 = precursor_sha1
 
100
                        updated = True
 
101
                    else:
 
102
                        #mutter('    checking precursor hash {%s}' % rev.precursor_sha1)
 
103
                        if rev.precursor_sha1 != precursor_sha1:
 
104
                            raise BzrCheckError('Precursor sha1 hash doesn\'t match'
 
105
                                ' value in revision {%s}' % rid)
 
106
                elif update:
 
107
                    precursor_sha1 = branch.get_revision_sha1(rev.precursor)
 
108
                    rev.precursor_sha1 = precursor_sha1
 
109
                    updated = True
 
110
 
 
111
            if updated:
 
112
                updated_previous_revision = True
 
113
                # We had to update this revision entries hashes
 
114
                # Now we need to write out a new value
 
115
                # This is a little bit invasive, since we are *rewriting* a
 
116
                # revision entry. I'm not supremely happy about it, but
 
117
                # there should be *some* way of making old entries have
 
118
                # the full meta information.
 
119
                import tempfile, os, errno
 
120
                rev_tmp = tempfile.TemporaryFile()
 
121
                rev.write_xml(rev_tmp)
 
122
                rev_tmp.seek(0)
 
123
 
 
124
                tmpfd, tmp_path = tempfile.mkstemp(prefix=rid, suffix='.gz',
 
125
                    dir=branch.controlfilename('revision-store'))
 
126
                os.close(tmpfd)
 
127
                def special_rename(p1, p2):
 
128
                    if sys.platform == 'win32':
 
129
                        try:
 
130
                            os.remove(p2)
 
131
                        except OSError, e:
 
132
                            if e.errno != e.ENOENT:
 
133
                                raise
 
134
                    os.rename(p1, p2)
 
135
 
 
136
                try:
 
137
                    # TODO: We may need to handle the case where the old revision
 
138
                    # entry was not compressed (and thus did not end with .gz)
 
139
 
 
140
                    # Remove the old revision entry out of the way
 
141
                    rev_path = branch.controlfilename(['revision-store', rid+'.gz'])
 
142
                    special_rename(rev_path, tmp_path)
 
143
                    branch.revision_store.add(rev_tmp, rid) # Add the new one
 
144
                    os.remove(tmp_path) # Remove the old name
 
145
                    mutter('    Updated revision entry {%s}' % rid)
 
146
                except:
 
147
                    # On any exception, restore the old entry
 
148
                    special_rename(tmp_path, rev_path)
 
149
                    raise
 
150
                rev_tmp.close()
 
151
                updated_revisions.append(rid)
 
152
            else:
 
153
                updated_previous_revision = False
 
154
 
 
155
            inv = branch.get_inventory(rev.inventory_id)
 
156
            seen_ids = {}
 
157
            seen_names = {}
 
158
 
 
159
            ## p('revision %d/%d file ids' % (revno, revcount))
 
160
            for file_id in inv:
 
161
                if file_id in seen_ids:
 
162
                    raise BzrCheckError('duplicated file_id {%s} '
 
163
                                        'in inventory for revision {%s}'
 
164
                                        % (file_id, rid))
 
165
                seen_ids[file_id] = True
 
166
 
 
167
            i = 0
 
168
            len_inv = len(inv)
 
169
            for file_id in inv:
 
170
                i += 1
 
171
                if i & 31 == 0:
 
172
                    pb.tick()
 
173
 
 
174
                ie = inv[file_id]
 
175
 
 
176
                if ie.parent_id != None:
 
177
                    if ie.parent_id not in seen_ids:
 
178
                        raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
179
                                % (ie.parent_id, rid))
 
180
 
 
181
                if ie.kind == 'file':
 
182
                    if ie.text_id in checked_texts:
 
183
                        fp = checked_texts[ie.text_id]
 
184
                    else:
 
185
                        if not ie.text_id in branch.text_store:
 
186
                            raise BzrCheckError('text {%s} not in text_store' % ie.text_id)
 
187
 
 
188
                        tf = branch.text_store[ie.text_id]
 
189
                        fp = fingerprint_file(tf)
 
190
                        checked_texts[ie.text_id] = fp
 
191
 
 
192
                    if ie.text_size != fp['size']:
 
193
                        raise BzrCheckError('text {%s} wrong size' % ie.text_id)
 
194
                    if ie.text_sha1 != fp['sha1']:
 
195
                        raise BzrCheckError('text {%s} wrong sha1' % ie.text_id)
 
196
                elif ie.kind == 'directory':
 
197
                    if ie.text_sha1 != None or ie.text_size != None or ie.text_id != None:
 
198
                        raise BzrCheckError('directory {%s} has text in revision {%s}'
 
199
                                % (file_id, rid))
 
200
 
 
201
            pb.tick()
 
202
            for path, ie in inv.iter_entries():
 
203
                if path in seen_names:
 
204
                    raise BzrCheckError('duplicated path %r '
 
205
                                        'in inventory for revision {%s}'
 
206
                                        % (path, revid))
 
207
            seen_names[path] = True
 
208
 
 
209
    finally:
 
210
        branch.unlock()
 
211
 
 
212
    pb.clear()
 
213
 
 
214
    print 'checked %d revisions, %d file texts' % (revcount, len(checked_texts))
 
215
    if updated_revisions:
 
216
        print '%d revisions updated to current format' % len(updated_revisions)
 
217
    if missing_inventory_sha_cnt:
 
218
        print '%d revisions are missing inventory_sha1' % missing_inventory_sha_cnt
 
219
        print '  (use bzr check --update to fix them)'