~bzr-pqm/bzr/bzr.dev

1 by mbp at sourcefrog
import from baz patch-364
1
# Copyright (C) 2004, 2005 by Martin Pool
2
# Copyright (C) 2005 by Canonical Ltd
3
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
18
19
20
674 by Martin Pool
- check command now also checks new inventory_sha1 and
21
def check(branch, update=False):
654 by Martin Pool
- update check command to use aaron's progress code
22
    """Run consistency checks on a branch.
674 by Martin Pool
- check command now also checks new inventory_sha1 and
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.
654 by Martin Pool
- update check command to use aaron's progress code
27
    """
651 by Martin Pool
- clean up imports
28
    import sys
29
30
    from bzrlib.trace import mutter
31
    from bzrlib.errors import BzrCheckError
32
    from bzrlib.osutils import fingerprint_file
658 by Martin Pool
- clean up and add a bunch of options to the progress indicator
33
    from bzrlib.progress import ProgressBar
676 by Martin Pool
- lock branch while checking
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)
674 by Martin Pool
- check command now also checks new inventory_sha1 and
108
                    rev.precursor_sha1 = precursor_sha1
109
                    updated = True
676 by Martin Pool
- lock branch while checking
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))
543 by Martin Pool
- More cleanups for set type
207
            seen_names[path] = True
125 by mbp at sourcefrog
- check progress indicator for file texts
208
676 by Martin Pool
- lock branch while checking
209
    finally:
210
        branch.unlock()
121 by mbp at sourcefrog
- progress indicator while checking
211
654 by Martin Pool
- update check command to use aaron's progress code
212
    pb.clear()
674 by Martin Pool
- check command now also checks new inventory_sha1 and
213
248 by mbp at sourcefrog
- Better progress and completion indicator from check command
214
    print 'checked %d revisions, %d file texts' % (revcount, len(checked_texts))
674 by Martin Pool
- check command now also checks new inventory_sha1 and
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)'