mercurial/verify.py
changeset 2802 fdc232d8a193
child 3189 f3b939444c72
equal deleted inserted replaced
2801:81d7db1aa0fb 2802:fdc232d8a193
       
     1 # verify.py - repository integrity checking for Mercurial
       
     2 #
       
     3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms
       
     6 # of the GNU General Public License, incorporated herein by reference.
       
     7 
       
     8 from node import *
       
     9 from i18n import gettext as _
       
    10 import revlog, mdiff
       
    11 
       
    12 def verify(repo):
       
    13     filelinkrevs = {}
       
    14     filenodes = {}
       
    15     changesets = revisions = files = 0
       
    16     errors = [0]
       
    17     warnings = [0]
       
    18     neededmanifests = {}
       
    19 
       
    20     def err(msg):
       
    21         repo.ui.warn(msg + "\n")
       
    22         errors[0] += 1
       
    23 
       
    24     def warn(msg):
       
    25         repo.ui.warn(msg + "\n")
       
    26         warnings[0] += 1
       
    27 
       
    28     def checksize(obj, name):
       
    29         d = obj.checksize()
       
    30         if d[0]:
       
    31             err(_("%s data length off by %d bytes") % (name, d[0]))
       
    32         if d[1]:
       
    33             err(_("%s index contains %d extra bytes") % (name, d[1]))
       
    34 
       
    35     def checkversion(obj, name):
       
    36         if obj.version != revlog.REVLOGV0:
       
    37             if not revlogv1:
       
    38                 warn(_("warning: `%s' uses revlog format 1") % name)
       
    39         elif revlogv1:
       
    40             warn(_("warning: `%s' uses revlog format 0") % name)
       
    41 
       
    42     revlogv1 = repo.revlogversion != revlog.REVLOGV0
       
    43     if repo.ui.verbose or revlogv1 != repo.revlogv1:
       
    44         repo.ui.status(_("repository uses revlog format %d\n") %
       
    45                        (revlogv1 and 1 or 0))
       
    46 
       
    47     seen = {}
       
    48     repo.ui.status(_("checking changesets\n"))
       
    49     checksize(repo.changelog, "changelog")
       
    50 
       
    51     for i in range(repo.changelog.count()):
       
    52         changesets += 1
       
    53         n = repo.changelog.node(i)
       
    54         l = repo.changelog.linkrev(n)
       
    55         if l != i:
       
    56             err(_("incorrect link (%d) for changeset revision %d") %(l, i))
       
    57         if n in seen:
       
    58             err(_("duplicate changeset at revision %d") % i)
       
    59         seen[n] = 1
       
    60 
       
    61         for p in repo.changelog.parents(n):
       
    62             if p not in repo.changelog.nodemap:
       
    63                 err(_("changeset %s has unknown parent %s") %
       
    64                              (short(n), short(p)))
       
    65         try:
       
    66             changes = repo.changelog.read(n)
       
    67         except KeyboardInterrupt:
       
    68             repo.ui.warn(_("interrupted"))
       
    69             raise
       
    70         except Exception, inst:
       
    71             err(_("unpacking changeset %s: %s") % (short(n), inst))
       
    72             continue
       
    73 
       
    74         neededmanifests[changes[0]] = n
       
    75 
       
    76         for f in changes[3]:
       
    77             filelinkrevs.setdefault(f, []).append(i)
       
    78 
       
    79     seen = {}
       
    80     repo.ui.status(_("checking manifests\n"))
       
    81     checkversion(repo.manifest, "manifest")
       
    82     checksize(repo.manifest, "manifest")
       
    83 
       
    84     for i in range(repo.manifest.count()):
       
    85         n = repo.manifest.node(i)
       
    86         l = repo.manifest.linkrev(n)
       
    87 
       
    88         if l < 0 or l >= repo.changelog.count():
       
    89             err(_("bad manifest link (%d) at revision %d") % (l, i))
       
    90 
       
    91         if n in neededmanifests:
       
    92             del neededmanifests[n]
       
    93 
       
    94         if n in seen:
       
    95             err(_("duplicate manifest at revision %d") % i)
       
    96 
       
    97         seen[n] = 1
       
    98 
       
    99         for p in repo.manifest.parents(n):
       
   100             if p not in repo.manifest.nodemap:
       
   101                 err(_("manifest %s has unknown parent %s") %
       
   102                     (short(n), short(p)))
       
   103 
       
   104         try:
       
   105             delta = mdiff.patchtext(repo.manifest.delta(n))
       
   106         except KeyboardInterrupt:
       
   107             repo.ui.warn(_("interrupted"))
       
   108             raise
       
   109         except Exception, inst:
       
   110             err(_("unpacking manifest %s: %s") % (short(n), inst))
       
   111             continue
       
   112 
       
   113         try:
       
   114             ff = [ l.split('\0') for l in delta.splitlines() ]
       
   115             for f, fn in ff:
       
   116                 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
       
   117         except (ValueError, TypeError), inst:
       
   118             err(_("broken delta in manifest %s: %s") % (short(n), inst))
       
   119 
       
   120     repo.ui.status(_("crosschecking files in changesets and manifests\n"))
       
   121 
       
   122     for m, c in neededmanifests.items():
       
   123         err(_("Changeset %s refers to unknown manifest %s") %
       
   124             (short(m), short(c)))
       
   125     del neededmanifests
       
   126 
       
   127     for f in filenodes:
       
   128         if f not in filelinkrevs:
       
   129             err(_("file %s in manifest but not in changesets") % f)
       
   130 
       
   131     for f in filelinkrevs:
       
   132         if f not in filenodes:
       
   133             err(_("file %s in changeset but not in manifest") % f)
       
   134 
       
   135     repo.ui.status(_("checking files\n"))
       
   136     ff = filenodes.keys()
       
   137     ff.sort()
       
   138     for f in ff:
       
   139         if f == "/dev/null":
       
   140             continue
       
   141         files += 1
       
   142         if not f:
       
   143             err(_("file without name in manifest %s") % short(n))
       
   144             continue
       
   145         fl = repo.file(f)
       
   146         checkversion(fl, f)
       
   147         checksize(fl, f)
       
   148 
       
   149         nodes = {nullid: 1}
       
   150         seen = {}
       
   151         for i in range(fl.count()):
       
   152             revisions += 1
       
   153             n = fl.node(i)
       
   154 
       
   155             if n in seen:
       
   156                 err(_("%s: duplicate revision %d") % (f, i))
       
   157             if n not in filenodes[f]:
       
   158                 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
       
   159             else:
       
   160                 del filenodes[f][n]
       
   161 
       
   162             flr = fl.linkrev(n)
       
   163             if flr not in filelinkrevs.get(f, []):
       
   164                 err(_("%s:%s points to unexpected changeset %d")
       
   165                         % (f, short(n), flr))
       
   166             else:
       
   167                 filelinkrevs[f].remove(flr)
       
   168 
       
   169             # verify contents
       
   170             try:
       
   171                 t = fl.read(n)
       
   172             except KeyboardInterrupt:
       
   173                 repo.ui.warn(_("interrupted"))
       
   174                 raise
       
   175             except Exception, inst:
       
   176                 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
       
   177 
       
   178             # verify parents
       
   179             (p1, p2) = fl.parents(n)
       
   180             if p1 not in nodes:
       
   181                 err(_("file %s:%s unknown parent 1 %s") %
       
   182                     (f, short(n), short(p1)))
       
   183             if p2 not in nodes:
       
   184                 err(_("file %s:%s unknown parent 2 %s") %
       
   185                         (f, short(n), short(p1)))
       
   186             nodes[n] = 1
       
   187 
       
   188         # cross-check
       
   189         for node in filenodes[f]:
       
   190             err(_("node %s in manifests not in %s") % (hex(node), f))
       
   191 
       
   192     repo.ui.status(_("%d files, %d changesets, %d total revisions\n") %
       
   193                    (files, changesets, revisions))
       
   194 
       
   195     if warnings[0]:
       
   196         repo.ui.warn(_("%d warnings encountered!\n") % warnings[0])
       
   197     if errors[0]:
       
   198         repo.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
       
   199         return 1
       
   200