~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: Martin Pool
  • Date: 2005-06-15 06:03:16 UTC
  • Revision ID: mbp@sourcefrog.net-20050615060316-97b948fb1eade31f
Tags: bzr-0.0.5
0.0.5 release

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/python
2
 
 
3
 
 
4
1
# Copyright (C) 2004, 2005 by Martin Pool
5
2
# Copyright (C) 2005 by Canonical Ltd
6
3
 
7
 
 
8
4
# This program is free software; you can redistribute it and/or modify
9
5
# it under the terms of the GNU General Public License as published by
10
6
# the Free Software Foundation; either version 2 of the License, or
21
17
 
22
18
 
23
19
 
24
 
######################################################################
25
 
# consistency checks
26
 
 
27
 
def check():
28
 
    """Consistency check of tree."""
29
 
    assert_in_tree()
30
 
    mutter("checking tree")
31
 
    check_patches_exist()
32
 
    check_patch_chaining()
33
 
    check_patch_uniqueness()
34
 
    check_inventory()
35
 
    mutter("tree looks OK")
36
 
    ## TODO: Check that previous-inventory and previous-manifest
37
 
    ## are the same as those stored in the previous changeset.
38
 
 
39
 
    ## TODO: Check all patches present in patch directory are
40
 
    ## mentioned in patch history; having an orphaned patch only gives
41
 
    ## a warning.
42
 
 
43
 
    ## TODO: Check cached data is consistent with data reconstructed
44
 
    ## from scratch.
45
 
 
46
 
    ## TODO: Check no control files are versioned.
47
 
 
48
 
    ## TODO: Check that the before-hash of each file in a later
49
 
    ## revision matches the after-hash in the previous revision to
50
 
    ## touch it.
51
 
 
52
 
 
53
 
def check_inventory():
54
 
    mutter("checking inventory file and ids...")
55
 
    seen_ids = Set()
56
 
    seen_names = Set()
57
 
    
58
 
    for l in controlfile('inventory').readlines():
59
 
        parts = l.split()
60
 
        if len(parts) != 2:
61
 
            bailout("malformed inventory line: " + `l`)
62
 
        file_id, name = parts
63
 
        
64
 
        if file_id in seen_ids:
65
 
            bailout("duplicated file id " + file_id)
66
 
        seen_ids.add(file_id)
67
 
 
68
 
        if name in seen_names:
69
 
            bailout("duplicated file name in inventory: " + quotefn(name))
70
 
        seen_names.add(name)
71
 
        
72
 
        if is_control_file(name):
73
 
            raise BzrError("control file %s present in inventory" % quotefn(name))
74
 
 
75
 
 
76
 
def check_patches_exist():
77
 
    """Check constraint of current version: all patches exist"""
78
 
    mutter("checking all patches are present...")
79
 
    for pid in revision_history():
80
 
        read_patch_header(pid)
81
 
 
82
 
 
83
 
def check_patch_chaining():
84
 
    """Check ancestry of patches and history file is consistent"""
85
 
    mutter("checking patch chaining...")
86
 
    prev = None
87
 
    for pid in revision_history():
88
 
        log_prev = read_patch_header(pid).precursor
89
 
        if log_prev != prev:
90
 
            bailout("inconsistent precursor links on " + pid)
91
 
        prev = pid
92
 
 
93
 
 
94
 
def check_patch_uniqueness():
95
 
    """Make sure no patch is listed twice in the history.
96
 
 
97
 
    This should be implied by having correct ancestry but I'll check it
98
 
    anyhow."""
99
 
    mutter("checking history for duplicates...")
100
 
    seen = Set()
101
 
    for pid in revision_history():
102
 
        if pid in seen:
103
 
            bailout("patch " + pid + " appears twice in history")
104
 
        seen.add(pid)
105
 
        
106
 
 
 
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()
 
37
    else:
 
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)'