~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/check.py

  • Committer: Robert Collins
  • Date: 2005-09-23 09:25:16 UTC
  • mto: (1092.3.4)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: robertc@robertcollins.net-20050923092516-e2c3c0f31288669d
Merge what applied of Alexander Belchenko's win32 patch.

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