~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: Martin Pool
  • Date: 2005-07-22 19:44:09 UTC
  • Revision ID: mbp@sourcefrog.net-20050722194409-9f99ffe3d62cf8c0
- tidy up imports to use full module names

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
18
 
19
 
 
20
 
 
21
 
def check(branch, update=False):
 
19
def _update_store_entry(obj, obj_id, branch, store_name, store):
 
20
    """This is just a meta-function, which handles both revision entries
 
21
    and inventory entries.
 
22
    """
 
23
    from bzrlib.trace import mutter
 
24
    import tempfile, os, errno
 
25
    from osutils import rename
 
26
    obj_tmp = tempfile.TemporaryFile()
 
27
    obj.write_xml(obj_tmp)
 
28
    obj_tmp.seek(0)
 
29
 
 
30
    tmpfd, tmp_path = tempfile.mkstemp(prefix=obj_id, suffix='.gz',
 
31
        dir=branch.controlfilename(store_name))
 
32
    os.close(tmpfd)
 
33
    try:
 
34
        orig_obj_path = branch.controlfilename([store_name, obj_id+'.gz'])
 
35
        # Remove the old entry out of the way
 
36
        rename(orig_obj_path, tmp_path)
 
37
        try:
 
38
            # TODO: We may need to handle the case where the old
 
39
            # entry was not compressed (and thus did not end with .gz)
 
40
 
 
41
            store.add(obj_tmp, obj_id) # Add the new one
 
42
            os.remove(tmp_path) # Remove the old name
 
43
            mutter('    Updated %s entry {%s}' % (store_name, obj_id))
 
44
        except:
 
45
            # On any exception, restore the old entry
 
46
            rename(tmp_path, orig_obj_path)
 
47
            raise
 
48
    finally:
 
49
        if os.path.exists(tmp_path):
 
50
            # Unfortunately, the next command might throw
 
51
            # an exception, which will mask a previous exception.
 
52
            os.remove(tmp_path)
 
53
        obj_tmp.close()
 
54
 
 
55
def _update_revision_entry(rev, branch):
 
56
    """After updating the values in a revision, make sure to
 
57
    write out the data, but try to do it in an atomic manner.
 
58
 
 
59
    :param rev:    The Revision object to store
 
60
    :param branch: The Branch object where this Revision is to be stored.
 
61
    """
 
62
    _update_store_entry(rev, rev.revision_id, branch,
 
63
            'revision-store', branch.revision_store)
 
64
 
 
65
def _update_inventory_entry(inv, inv_id, branch):
 
66
    """When an inventory has been modified (such as by adding a unique tree root)
 
67
    this atomically re-generates the file.
 
68
 
 
69
    :param inv:     The Inventory
 
70
    :param inv_id:  The inventory id for this inventory
 
71
    :param branch:  The Branch where this entry will be stored.
 
72
    """
 
73
    _update_store_entry(inv, inv_id, branch,
 
74
            'inventory-store', branch.inventory_store)
 
75
 
 
76
def check(branch):
22
77
    """Run consistency checks on a branch.
23
78
 
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.
 
79
    TODO: Also check non-mainline revisions mentioned as parents.
 
80
 
 
81
    TODO: Check for extra files in the control directory.
27
82
    """
28
 
    import sys
29
 
 
30
83
    from bzrlib.trace import mutter
31
84
    from bzrlib.errors import BzrCheckError
32
85
    from bzrlib.osutils import fingerprint_file
33
86
    from bzrlib.progress import ProgressBar
 
87
    from bzrlib.inventory import ROOT_ID
 
88
    from bzrlib.branch import gen_root_id
34
89
 
35
 
    if update:
36
 
        branch.lock_write()
37
 
    else:
38
 
        branch.lock_read()
 
90
    branch.lock_read()
39
91
 
40
92
    try:
41
 
 
42
 
        out = sys.stdout
43
 
 
44
93
        pb = ProgressBar(show_spinner=True)
45
 
        last_ptr = None
46
 
        checked_revs = {}
 
94
        last_rev_id = None
47
95
 
48
96
        missing_inventory_sha_cnt = 0
 
97
        missing_revision_sha_cnt = 0
49
98
 
50
99
        history = branch.revision_history()
51
100
        revno = 0
52
101
        revcount = len(history)
 
102
        mismatch_inv_id = []
53
103
 
 
104
        # for all texts checked, text_id -> sha1
54
105
        checked_texts = {}
55
106
 
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:
 
107
        for rev_id in history:
63
108
            revno += 1
64
109
            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
 
110
            mutter('    revision {%s}' % rev_id)
 
111
            rev = branch.get_revision(rev_id)
 
112
            if rev.revision_id != rev_id:
 
113
                raise BzrCheckError('wrong internal revision id in revision {%s}'
 
114
                                    % rev_id)
 
115
 
 
116
            # check the previous history entry is a parent of this entry
 
117
            if rev.parents:
 
118
                if last_rev_id is None:
 
119
                    raise BzrCheckError("revision {%s} has %d parents, but is the "
 
120
                                        "start of the branch"
 
121
                                        % (rev_id, len(rev.parents)))
 
122
                for prr in rev.parents:
 
123
                    if prr.revision_id == last_rev_id:
 
124
                        break
 
125
                else:
 
126
                    raise BzrCheckError("previous revision {%s} not listed among "
 
127
                                        "parents of {%s}"
 
128
                                        % (last_rev_id, rev_id))
 
129
 
 
130
                for prr in rev.parents:
 
131
                    if prr.revision_sha1 is None:
 
132
                        missing_revision_sha_cnt += 1
 
133
                        continue
 
134
                    prid = prr.revision_id
 
135
                    actual_sha = branch.get_revision_sha1(prid)
 
136
                    if prr.revision_sha1 != actual_sha:
 
137
                        raise BzrCheckError("mismatched revision sha1 for "
 
138
                                            "parent {%s} of {%s}: %s vs %s"
 
139
                                            % (prid, rev_id,
 
140
                                               prr.revision_sha1, actual_sha))
 
141
            elif last_rev_id:
 
142
                raise BzrCheckError("revision {%s} has no parents listed but preceded "
 
143
                                    "by {%s}"
 
144
                                    % (rev_id, last_rev_id))
 
145
 
 
146
            if rev.inventory_id != rev_id:
 
147
                mismatch_inv_id.append(rev_id)
75
148
 
76
149
            ## TODO: Check all the required fields are present on the revision.
77
150
 
78
 
            updated = False
79
151
            if rev.inventory_sha1:
80
 
                #mutter('    checking inventory hash {%s}' % rev.inventory_sha1)
81
152
                inv_sha1 = branch.get_inventory_sha1(rev.inventory_id)
82
153
                if inv_sha1 != rev.inventory_sha1:
83
154
                    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
 
155
                        ' value in revision {%s}' % rev_id)
89
156
            else:
90
157
                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
 
158
                mutter("no inventory_sha1 on revision {%s}" % rev_id)
154
159
 
155
160
            inv = branch.get_inventory(rev.inventory_id)
156
161
            seen_ids = {}
161
166
                if file_id in seen_ids:
162
167
                    raise BzrCheckError('duplicated file_id {%s} '
163
168
                                        'in inventory for revision {%s}'
164
 
                                        % (file_id, rid))
 
169
                                        % (file_id, rev_id))
165
170
                seen_ids[file_id] = True
166
171
 
167
172
            i = 0
168
 
            len_inv = len(inv)
169
173
            for file_id in inv:
170
174
                i += 1
171
175
                if i & 31 == 0:
176
180
                if ie.parent_id != None:
177
181
                    if ie.parent_id not in seen_ids:
178
182
                        raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
179
 
                                % (ie.parent_id, rid))
 
183
                                % (ie.parent_id, rev_id))
180
184
 
181
185
                if ie.kind == 'file':
182
186
                    if ie.text_id in checked_texts:
196
200
                elif ie.kind == 'directory':
197
201
                    if ie.text_sha1 != None or ie.text_size != None or ie.text_id != None:
198
202
                        raise BzrCheckError('directory {%s} has text in revision {%s}'
199
 
                                % (file_id, rid))
 
203
                                % (file_id, rev_id))
200
204
 
201
205
            pb.tick()
202
206
            for path, ie in inv.iter_entries():
203
207
                if path in seen_names:
204
 
                    raise BzrCheckError('duplicated path %r '
 
208
                    raise BzrCheckError('duplicated path %s '
205
209
                                        'in inventory for revision {%s}'
206
 
                                        % (path, revid))
 
210
                                        % (path, rev_id))
207
211
            seen_names[path] = True
 
212
            last_rev_id = rev_id
208
213
 
209
214
    finally:
210
215
        branch.unlock()
212
217
    pb.clear()
213
218
 
214
219
    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)
 
220
    
217
221
    if missing_inventory_sha_cnt:
218
222
        print '%d revisions are missing inventory_sha1' % missing_inventory_sha_cnt
219
 
        print '  (use bzr check --update to fix them)'
 
223
 
 
224
    if missing_revision_sha_cnt:
 
225
        print '%d parent links are missing revision_sha1' % missing_revision_sha_cnt
 
226
 
 
227
    if (missing_inventory_sha_cnt
 
228
        or missing_revision_sha_cnt):
 
229
        print '  (use "bzr upgrade" to fix them)'
 
230
 
 
231
    if mismatch_inv_id:
 
232
        print '%d revisions have mismatched inventory ids:' % len(mismatch_inv_id)
 
233
        for rev_id in mismatch_inv_id:
 
234
            print '  ', rev_id