eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/bundlerepo.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # bundlerepo.py - repository class for viewing uncompressed bundles
       
     2 #
       
     3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 """Repository class for viewing uncompressed bundles.
       
     9 
       
    10 This provides a read-only repository interface to bundles as if they
       
    11 were part of the actual repository.
       
    12 """
       
    13 
       
    14 from node import nullid
       
    15 from i18n import _
       
    16 import os, struct, tempfile, shutil
       
    17 import changegroup, util, mdiff, discovery
       
    18 import localrepo, changelog, manifest, filelog, revlog, error
       
    19 
       
    20 class bundlerevlog(revlog.revlog):
       
    21     def __init__(self, opener, indexfile, bundle,
       
    22                  linkmapper=None):
       
    23         # How it works:
       
    24         # to retrieve a revision, we need to know the offset of
       
    25         # the revision in the bundle (an unbundle object).
       
    26         #
       
    27         # We store this offset in the index (start), to differentiate a
       
    28         # rev in the bundle and from a rev in the revlog, we check
       
    29         # len(index[r]). If the tuple is bigger than 7, it is a bundle
       
    30         # (it is bigger since we store the node to which the delta is)
       
    31         #
       
    32         revlog.revlog.__init__(self, opener, indexfile)
       
    33         self.bundle = bundle
       
    34         self.basemap = {}
       
    35         def chunkpositer():
       
    36             while 1:
       
    37                 chunk = bundle.chunk()
       
    38                 if not chunk:
       
    39                     break
       
    40                 pos = bundle.tell()
       
    41                 yield chunk, pos - len(chunk)
       
    42         n = len(self)
       
    43         prev = None
       
    44         for chunk, start in chunkpositer():
       
    45             size = len(chunk)
       
    46             if size < 80:
       
    47                 raise util.Abort(_("invalid changegroup"))
       
    48             start += 80
       
    49             size -= 80
       
    50             node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
       
    51             if node in self.nodemap:
       
    52                 prev = node
       
    53                 continue
       
    54             for p in (p1, p2):
       
    55                 if not p in self.nodemap:
       
    56                     raise error.LookupError(p, self.indexfile,
       
    57                                             _("unknown parent"))
       
    58             if linkmapper is None:
       
    59                 link = n
       
    60             else:
       
    61                 link = linkmapper(cs)
       
    62 
       
    63             if not prev:
       
    64                 prev = p1
       
    65             # start, size, full unc. size, base (unused), link, p1, p2, node
       
    66             e = (revlog.offset_type(start, 0), size, -1, -1, link,
       
    67                  self.rev(p1), self.rev(p2), node)
       
    68             self.basemap[n] = prev
       
    69             self.index.insert(-1, e)
       
    70             self.nodemap[node] = n
       
    71             prev = node
       
    72             n += 1
       
    73 
       
    74     def inbundle(self, rev):
       
    75         """is rev from the bundle"""
       
    76         if rev < 0:
       
    77             return False
       
    78         return rev in self.basemap
       
    79     def bundlebase(self, rev):
       
    80         return self.basemap[rev]
       
    81     def _chunk(self, rev):
       
    82         # Warning: in case of bundle, the diff is against bundlebase,
       
    83         # not against rev - 1
       
    84         # XXX: could use some caching
       
    85         if not self.inbundle(rev):
       
    86             return revlog.revlog._chunk(self, rev)
       
    87         self.bundle.seek(self.start(rev))
       
    88         return self.bundle.read(self.length(rev))
       
    89 
       
    90     def revdiff(self, rev1, rev2):
       
    91         """return or calculate a delta between two revisions"""
       
    92         if self.inbundle(rev1) and self.inbundle(rev2):
       
    93             # hot path for bundle
       
    94             revb = self.rev(self.bundlebase(rev2))
       
    95             if revb == rev1:
       
    96                 return self._chunk(rev2)
       
    97         elif not self.inbundle(rev1) and not self.inbundle(rev2):
       
    98             return revlog.revlog.revdiff(self, rev1, rev2)
       
    99 
       
   100         return mdiff.textdiff(self.revision(self.node(rev1)),
       
   101                          self.revision(self.node(rev2)))
       
   102 
       
   103     def revision(self, node):
       
   104         """return an uncompressed revision of a given"""
       
   105         if node == nullid:
       
   106             return ""
       
   107 
       
   108         text = None
       
   109         chain = []
       
   110         iter_node = node
       
   111         rev = self.rev(iter_node)
       
   112         # reconstruct the revision if it is from a changegroup
       
   113         while self.inbundle(rev):
       
   114             if self._cache and self._cache[0] == iter_node:
       
   115                 text = self._cache[2]
       
   116                 break
       
   117             chain.append(rev)
       
   118             iter_node = self.bundlebase(rev)
       
   119             rev = self.rev(iter_node)
       
   120         if text is None:
       
   121             text = revlog.revlog.revision(self, iter_node)
       
   122 
       
   123         while chain:
       
   124             delta = self._chunk(chain.pop())
       
   125             text = mdiff.patches(text, [delta])
       
   126 
       
   127         p1, p2 = self.parents(node)
       
   128         if node != revlog.hash(text, p1, p2):
       
   129             raise error.RevlogError(_("integrity check failed on %s:%d")
       
   130                                      % (self.datafile, self.rev(node)))
       
   131 
       
   132         self._cache = (node, self.rev(node), text)
       
   133         return text
       
   134 
       
   135     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
       
   136         raise NotImplementedError
       
   137     def addgroup(self, revs, linkmapper, transaction):
       
   138         raise NotImplementedError
       
   139     def strip(self, rev, minlink):
       
   140         raise NotImplementedError
       
   141     def checksize(self):
       
   142         raise NotImplementedError
       
   143 
       
   144 class bundlechangelog(bundlerevlog, changelog.changelog):
       
   145     def __init__(self, opener, bundle):
       
   146         changelog.changelog.__init__(self, opener)
       
   147         bundlerevlog.__init__(self, opener, self.indexfile, bundle)
       
   148 
       
   149 class bundlemanifest(bundlerevlog, manifest.manifest):
       
   150     def __init__(self, opener, bundle, linkmapper):
       
   151         manifest.manifest.__init__(self, opener)
       
   152         bundlerevlog.__init__(self, opener, self.indexfile, bundle,
       
   153                               linkmapper)
       
   154 
       
   155 class bundlefilelog(bundlerevlog, filelog.filelog):
       
   156     def __init__(self, opener, path, bundle, linkmapper):
       
   157         filelog.filelog.__init__(self, opener, path)
       
   158         bundlerevlog.__init__(self, opener, self.indexfile, bundle,
       
   159                               linkmapper)
       
   160 
       
   161 class bundlerepository(localrepo.localrepository):
       
   162     def __init__(self, ui, path, bundlename):
       
   163         self._tempparent = None
       
   164         try:
       
   165             localrepo.localrepository.__init__(self, ui, path)
       
   166         except error.RepoError:
       
   167             self._tempparent = tempfile.mkdtemp()
       
   168             localrepo.instance(ui, self._tempparent, 1)
       
   169             localrepo.localrepository.__init__(self, ui, self._tempparent)
       
   170 
       
   171         if path:
       
   172             self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
       
   173         else:
       
   174             self._url = 'bundle:' + bundlename
       
   175 
       
   176         self.tempfile = None
       
   177         f = open(bundlename, "rb")
       
   178         self.bundle = changegroup.readbundle(f, bundlename)
       
   179         if self.bundle.compressed():
       
   180             fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
       
   181                                             suffix=".hg10un", dir=self.path)
       
   182             self.tempfile = temp
       
   183             fptemp = os.fdopen(fdtemp, 'wb')
       
   184 
       
   185             try:
       
   186                 fptemp.write("HG10UN")
       
   187                 while 1:
       
   188                     chunk = self.bundle.read(2**18)
       
   189                     if not chunk:
       
   190                         break
       
   191                     fptemp.write(chunk)
       
   192             finally:
       
   193                 fptemp.close()
       
   194 
       
   195             f = open(self.tempfile, "rb")
       
   196             self.bundle = changegroup.readbundle(f, bundlename)
       
   197 
       
   198         # dict with the mapping 'filename' -> position in the bundle
       
   199         self.bundlefilespos = {}
       
   200 
       
   201     @util.propertycache
       
   202     def changelog(self):
       
   203         c = bundlechangelog(self.sopener, self.bundle)
       
   204         self.manstart = self.bundle.tell()
       
   205         return c
       
   206 
       
   207     @util.propertycache
       
   208     def manifest(self):
       
   209         self.bundle.seek(self.manstart)
       
   210         m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
       
   211         self.filestart = self.bundle.tell()
       
   212         return m
       
   213 
       
   214     @util.propertycache
       
   215     def manstart(self):
       
   216         self.changelog
       
   217         return self.manstart
       
   218 
       
   219     @util.propertycache
       
   220     def filestart(self):
       
   221         self.manifest
       
   222         return self.filestart
       
   223 
       
   224     def url(self):
       
   225         return self._url
       
   226 
       
   227     def file(self, f):
       
   228         if not self.bundlefilespos:
       
   229             self.bundle.seek(self.filestart)
       
   230             while 1:
       
   231                 chunk = self.bundle.chunk()
       
   232                 if not chunk:
       
   233                     break
       
   234                 self.bundlefilespos[chunk] = self.bundle.tell()
       
   235                 while 1:
       
   236                     c = self.bundle.chunk()
       
   237                     if not c:
       
   238                         break
       
   239 
       
   240         if f[0] == '/':
       
   241             f = f[1:]
       
   242         if f in self.bundlefilespos:
       
   243             self.bundle.seek(self.bundlefilespos[f])
       
   244             return bundlefilelog(self.sopener, f, self.bundle,
       
   245                                  self.changelog.rev)
       
   246         else:
       
   247             return filelog.filelog(self.sopener, f)
       
   248 
       
   249     def close(self):
       
   250         """Close assigned bundle file immediately."""
       
   251         self.bundle.close()
       
   252         if self.tempfile is not None:
       
   253             os.unlink(self.tempfile)
       
   254 
       
   255     def __del__(self):
       
   256         del self.bundle
       
   257         if self.tempfile is not None:
       
   258             os.unlink(self.tempfile)
       
   259         if self._tempparent:
       
   260             shutil.rmtree(self._tempparent, True)
       
   261 
       
   262     def cancopy(self):
       
   263         return False
       
   264 
       
   265     def getcwd(self):
       
   266         return os.getcwd() # always outside the repo
       
   267 
       
   268 def instance(ui, path, create):
       
   269     if create:
       
   270         raise util.Abort(_('cannot create new bundle repository'))
       
   271     parentpath = ui.config("bundle", "mainreporoot", "")
       
   272     if parentpath:
       
   273         # Try to make the full path relative so we get a nice, short URL.
       
   274         # In particular, we don't want temp dir names in test outputs.
       
   275         cwd = os.getcwd()
       
   276         if parentpath == cwd:
       
   277             parentpath = ''
       
   278         else:
       
   279             cwd = os.path.join(cwd,'')
       
   280             if parentpath.startswith(cwd):
       
   281                 parentpath = parentpath[len(cwd):]
       
   282     path = util.drop_scheme('file', path)
       
   283     if path.startswith('bundle:'):
       
   284         path = util.drop_scheme('bundle', path)
       
   285         s = path.split("+", 1)
       
   286         if len(s) == 1:
       
   287             repopath, bundlename = parentpath, s[0]
       
   288         else:
       
   289             repopath, bundlename = s
       
   290     else:
       
   291         repopath, bundlename = parentpath, path
       
   292     return bundlerepository(ui, repopath, bundlename)
       
   293 
       
   294 def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False):
       
   295     tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
       
   296     common, incoming, rheads = tmp
       
   297     if not incoming:
       
   298         try:
       
   299             os.unlink(bundlename)
       
   300         except:
       
   301             pass
       
   302         return other, None, None
       
   303 
       
   304     bundle = None
       
   305     if bundlename or not other.local():
       
   306         # create a bundle (uncompressed if other repo is not local)
       
   307 
       
   308         if revs is None and other.capable('changegroupsubset'):
       
   309             revs = rheads
       
   310 
       
   311         if revs is None:
       
   312             cg = other.changegroup(incoming, "incoming")
       
   313         else:
       
   314             cg = other.changegroupsubset(incoming, revs, 'incoming')
       
   315         bundletype = other.local() and "HG10BZ" or "HG10UN"
       
   316         fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
       
   317         # keep written bundle?
       
   318         if bundlename:
       
   319             bundle = None
       
   320         if not other.local():
       
   321             # use the created uncompressed bundlerepo
       
   322             other = bundlerepository(ui, repo.root, fname)
       
   323     return (other, incoming, bundle)
       
   324