mercurial/bundlerepo.py
changeset 1942 9da45de3118d
child 1946 9fee186f7f0d
equal deleted inserted replaced
1941:7518823709a2 1942:9da45de3118d
       
     1 """
       
     2 bundlerepo.py - repository class for viewing uncompressed bundles
       
     3 
       
     4 This provides a read-only repository interface to bundles as if
       
     5 they were part of the actual repository.
       
     6 
       
     7 Copyright 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
       
     8 
       
     9 This software may be used and distributed according to the terms
       
    10 of the GNU General Public License, incorporated herein by reference.
       
    11 """
       
    12 
       
    13 from node import *
       
    14 from i18n import gettext as _
       
    15 from demandload import demandload
       
    16 demandload(globals(), "util os struct")
       
    17 
       
    18 from changelog import changelog
       
    19 from manifest import manifest
       
    20 from filelog import filelog
       
    21 from localrepo import localrepository
       
    22 from revlog import *
       
    23 
       
    24 def getchunk(source):
       
    25     """get a chunk from a group"""
       
    26     d = source.read(4)
       
    27     if not d:
       
    28         return ""
       
    29     l = struct.unpack(">l", d)[0]
       
    30     if l <= 4:
       
    31         return ""
       
    32     d = source.read(l - 4)
       
    33     if len(d) < l - 4:
       
    34         raise util.Abort(_("premature EOF reading chunk"
       
    35                            " (got %d bytes, expected %d)")
       
    36                           % (len(d), l - 4))
       
    37     return d
       
    38 
       
    39 class bundlerevlog(revlog):
       
    40     def __init__(self, opener, indexfile, datafile, bundlefile,
       
    41                  linkmapper=None):
       
    42         # How it works:
       
    43         # to retrieve a revision, we need to know the offset of
       
    44         # the revision in the bundlefile (an opened file).
       
    45         #
       
    46         # We store this offset in the index (start), to differentiate a
       
    47         # rev in the bundle and from a rev in the revlog, we check
       
    48         # len(index[r]). If the tuple is bigger than 7, it is a bundle
       
    49         # (it is bigger since we store the node to which the delta is)
       
    50         #
       
    51         revlog.__init__(self, opener, indexfile, datafile)
       
    52         self.bundlefile = bundlefile
       
    53         def genchunk():
       
    54             while 1:
       
    55                 pos = bundlefile.tell()
       
    56                 chunk = getchunk(bundlefile)
       
    57                 if not chunk:
       
    58                     break
       
    59                 yield chunk, pos + 4 # XXX struct.calcsize(">l") == 4
       
    60         n = self.count()
       
    61         prev = None
       
    62         for chunk, start in genchunk():
       
    63             size = len(chunk)
       
    64             if size < 80:
       
    65                 raise util.Abort("invalid changegroup")
       
    66             start += 80
       
    67             size -= 80
       
    68             node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
       
    69             if node in self.nodemap:
       
    70                 prev = node
       
    71                 continue
       
    72             for p in (p1, p2):
       
    73                 if not p in self.nodemap:
       
    74                     raise RevlogError(_("unknown parent %s") % short(p1))
       
    75             if linkmapper is None:
       
    76                 link = n
       
    77             else:
       
    78                 link = linkmapper(cs)
       
    79 
       
    80             if not prev:
       
    81                 prev = p1
       
    82             # start, size, base is not used, link, p1, p2, delta ref
       
    83             # warning: 
       
    84             e = (start, size, None, link, p1, p2, node, prev)
       
    85             self.index.append(e)
       
    86             self.nodemap[node] = n
       
    87             prev = node
       
    88             n += 1
       
    89 
       
    90     def bundle(self, rev):
       
    91         """is rev from the bundle"""
       
    92         if rev < 0:
       
    93             return False
       
    94         return len(self.index[rev]) > 7
       
    95     def bundlebase(self, rev): return self.index[rev][7]
       
    96     def chunk(self, rev):
       
    97         # Warning: in case of bundle, the diff is against bundlebase,
       
    98         # not against rev - 1
       
    99         # XXX: could use some caching
       
   100         if not self.bundle(rev):
       
   101             return revlog.chunk(self, rev)
       
   102         self.bundlefile.seek(self.start(rev))
       
   103         return self.bundlefile.read(self.length(rev))
       
   104 
       
   105     def revdiff(self, rev1, rev2):
       
   106         """return or calculate a delta between two revisions"""
       
   107         if self.bundle(rev1) and self.bundle(rev2):
       
   108             # hot path for bundle
       
   109             revb = self.rev(self.bundlebase(rev2))
       
   110             if revb == rev1:
       
   111                 return self.chunk(rev2)
       
   112         elif not self.bundle(rev1) and not self.bundle(rev2):
       
   113             return revlog.chunk(self, rev1, rev2)
       
   114 
       
   115         return self.diff(self.revision(self.node(rev1)),
       
   116                          self.revision(self.node(rev2)))
       
   117 
       
   118     def revision(self, node):
       
   119         """return an uncompressed revision of a given"""
       
   120         if node == nullid: return ""
       
   121 
       
   122         text = None
       
   123         chain = []
       
   124         iter_node = node
       
   125         rev = self.rev(iter_node)
       
   126         # reconstruct the revision if it is from a changegroup
       
   127         while self.bundle(rev):
       
   128             if self.cache and self.cache[0] == iter_node:
       
   129                 text = self.cache[2]
       
   130                 break
       
   131             chain.append(rev)
       
   132             iter_node = self.bundlebase(rev)
       
   133             rev = self.rev(iter_node)
       
   134         if text is None:
       
   135             text = revlog.revision(self, iter_node)
       
   136 
       
   137         while chain:
       
   138             delta = self.chunk(chain.pop())
       
   139             text = self.patches(text, [delta])
       
   140 
       
   141         p1, p2 = self.parents(node)
       
   142         if node != hash(text, p1, p2):
       
   143             raise RevlogError(_("integrity check failed on %s:%d")
       
   144                           % (self.datafile, self.rev(node)))
       
   145 
       
   146         self.cache = (node, rev, text)
       
   147         return text
       
   148 
       
   149     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
       
   150         raise NotImplementedError
       
   151     def addgroup(self, revs, linkmapper, transaction, unique=0):
       
   152         raise NotImplementedError
       
   153     def strip(self, rev, minlink):
       
   154         raise NotImplementedError
       
   155     def checksize(self):
       
   156         raise NotImplementedError
       
   157 
       
   158 class bundlechangelog(bundlerevlog, changelog):
       
   159     def __init__(self, opener, bundlefile):
       
   160         changelog.__init__(self, opener)
       
   161         bundlerevlog.__init__(self, opener, "00changelog.i", "00changelog.d",
       
   162                               bundlefile)
       
   163 
       
   164 class bundlemanifest(bundlerevlog, manifest):
       
   165     def __init__(self, opener, bundlefile, linkmapper):
       
   166         manifest.__init__(self, opener)
       
   167         bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
       
   168                               bundlefile, linkmapper)
       
   169 
       
   170 class bundlefilelog(bundlerevlog, filelog):
       
   171     def __init__(self, opener, path, bundlefile, linkmapper):
       
   172         filelog.__init__(self, opener, path)
       
   173         bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
       
   174                               bundlefile, linkmapper)
       
   175 
       
   176 class bundlerepository(localrepository):
       
   177     def __init__(self, ui, path, bundlename):
       
   178         localrepository.__init__(self, ui, path)
       
   179         f = open(bundlename, "rb")
       
   180         s = os.fstat(f.fileno())
       
   181         self.bundlefile = f
       
   182         header = self.bundlefile.read(4)
       
   183         if header == "HG10":
       
   184             raise util.Abort(_("%s: compressed bundle not supported")
       
   185                              % bundlename)
       
   186         elif header != "HG11":
       
   187             raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
       
   188         self.changelog = bundlechangelog(self.opener, self.bundlefile)
       
   189         self.manifest = bundlemanifest(self.opener, self.bundlefile,
       
   190                                        self.changelog.rev)
       
   191         # dict with the mapping 'filename' -> position in the bundle
       
   192         self.bundlefilespos = {}
       
   193         while 1:
       
   194                 f = getchunk(self.bundlefile)
       
   195                 if not f:
       
   196                     break
       
   197                 self.bundlefilespos[f] = self.bundlefile.tell()
       
   198                 while getchunk(self.bundlefile):
       
   199                     pass
       
   200 
       
   201     def dev(self):
       
   202         return -1
       
   203 
       
   204     def file(self, f):
       
   205         if f[0] == '/':
       
   206             f = f[1:]
       
   207         if f in self.bundlefilespos:
       
   208             self.bundlefile.seek(self.bundlefilespos[f])
       
   209             return bundlefilelog(self.opener, f, self.bundlefile,
       
   210                                  self.changelog.rev)
       
   211         else:
       
   212             return filelog(self.opener, f)
       
   213