changegroup.py
204 lines
| 6.1 KiB
| text/x-python
|
PythonLexer
/ mercurial / changegroup.py
Martin Geisler
|
r8226 | # changegroup.py - Mercurial changegroup manipulation functions | ||
# | ||||
# Copyright 2006 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Matt Mackall
|
r3877 | |||
Matt Mackall
|
r3891 | from i18n import _ | ||
Simon Heimberg
|
r8312 | import util | ||
import struct, os, bz2, zlib, tempfile | ||||
Thomas Arendsen Hein
|
r1981 | |||
def getchunk(source): | ||||
Greg Ward
|
r9437 | """return the next chunk from changegroup 'source' as a string""" | ||
Thomas Arendsen Hein
|
r1981 | d = source.read(4) | ||
if not d: | ||||
return "" | ||||
l = struct.unpack(">l", d)[0] | ||||
if l <= 4: | ||||
return "" | ||||
d = source.read(l - 4) | ||||
if len(d) < l - 4: | ||||
raise util.Abort(_("premature EOF reading chunk" | ||||
" (got %d bytes, expected %d)") | ||||
% (len(d), l - 4)) | ||||
return d | ||||
Matt Mackall
|
r5368 | def chunkheader(length): | ||
Greg Ward
|
r9437 | """return a changegroup chunk header (string)""" | ||
Matt Mackall
|
r5368 | return struct.pack(">l", length + 4) | ||
Thomas Arendsen Hein
|
r1981 | |||
def closechunk(): | ||||
Greg Ward
|
r9437 | """return a changegroup chunk header (string) for a zero-length chunk""" | ||
Thomas Arendsen Hein
|
r1981 | return struct.pack(">l", 0) | ||
Matt Mackall
|
r3659 | class nocompress(object): | ||
def compress(self, x): | ||||
return x | ||||
def flush(self): | ||||
return "" | ||||
Matt Mackall
|
r3662 | bundletypes = { | ||
Benoit Boissinot
|
r3704 | "": ("", nocompress), | ||
"HG10UN": ("HG10UN", nocompress), | ||||
Alexis S. L. Carvalho
|
r3762 | "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()), | ||
"HG10GZ": ("HG10GZ", lambda: zlib.compressobj()), | ||||
Matt Mackall
|
r3662 | } | ||
Dirkjan Ochtman
|
r10356 | def collector(cl, mmfs, files): | ||
# Gather information about changeset nodes going out in a bundle. | ||||
# We want to gather manifests needed and filelogs affected. | ||||
def collect(node): | ||||
c = cl.read(node) | ||||
Benoit Boissinot
|
r11648 | files.update(c[3]) | ||
Dirkjan Ochtman
|
r10356 | mmfs.setdefault(c[0], node) | ||
return collect | ||||
Martin Geisler
|
r9087 | # hgweb uses this list to communicate its preferred type | ||
Dirkjan Ochtman
|
r6152 | bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN'] | ||
Thomas Arendsen Hein
|
r3706 | def writebundle(cg, filename, bundletype): | ||
Matt Mackall
|
r3659 | """Write a bundle file and return its filename. | ||
Existing files will not be overwritten. | ||||
If no filename is specified, a temporary file is created. | ||||
bz2 compression can be turned off. | ||||
The bundle file will be deleted in case of errors. | ||||
""" | ||||
fh = None | ||||
cleanup = None | ||||
try: | ||||
if filename: | ||||
fh = open(filename, "wb") | ||||
else: | ||||
fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg") | ||||
fh = os.fdopen(fd, "wb") | ||||
cleanup = filename | ||||
Thomas Arendsen Hein
|
r3706 | header, compressor = bundletypes[bundletype] | ||
Benoit Boissinot
|
r3704 | fh.write(header) | ||
z = compressor() | ||||
Matt Mackall
|
r3662 | |||
Matt Mackall
|
r3659 | # parse the changegroup data, otherwise we will block | ||
# in case of sshrepo because we don't know the end of the stream | ||||
Matt Mackall
|
r12335 | # an empty chunkgroup is the end of the changegroup | ||
# a changegroup has at least 2 chunkgroups (changelog and manifest). | ||||
# after that, an empty chunkgroup is the end of the changegroup | ||||
Matt Mackall
|
r3659 | empty = False | ||
Alexis S. L. Carvalho
|
r5906 | count = 0 | ||
while not empty or count <= 2: | ||||
Matt Mackall
|
r3659 | empty = True | ||
Alexis S. L. Carvalho
|
r5906 | count += 1 | ||
Matt Mackall
|
r12335 | while 1: | ||
chunk = getchunk(cg) | ||||
if not chunk: | ||||
break | ||||
Matt Mackall
|
r3659 | empty = False | ||
Matt Mackall
|
r5368 | fh.write(z.compress(chunkheader(len(chunk)))) | ||
pos = 0 | ||||
while pos < len(chunk): | ||||
next = pos + 2**20 | ||||
fh.write(z.compress(chunk[pos:next])) | ||||
pos = next | ||||
Matt Mackall
|
r3659 | fh.write(z.compress(closechunk())) | ||
fh.write(z.flush()) | ||||
cleanup = None | ||||
return filename | ||||
finally: | ||||
if fh is not None: | ||||
fh.close() | ||||
if cleanup is not None: | ||||
os.unlink(cleanup) | ||||
Matt Mackall
|
r3660 | |||
Matt Mackall
|
r12041 | def decompressor(fh, alg): | ||
if alg == 'UN': | ||||
Dirkjan Ochtman
|
r6154 | return fh | ||
Matt Mackall
|
r12041 | elif alg == 'GZ': | ||
Dirkjan Ochtman
|
r6154 | def generator(f): | ||
zd = zlib.decompressobj() | ||||
for chunk in f: | ||||
yield zd.decompress(chunk) | ||||
Matt Mackall
|
r12041 | elif alg == 'BZ': | ||
Matt Mackall
|
r3660 | def generator(f): | ||
zd = bz2.BZ2Decompressor() | ||||
zd.decompress("BZ") | ||||
for chunk in util.filechunkiter(f, 4096): | ||||
yield zd.decompress(chunk) | ||||
Matt Mackall
|
r12041 | else: | ||
raise util.Abort("unknown bundle compression '%s'" % alg) | ||||
Matt Mackall
|
r12329 | return util.chunkbuffer(generator(fh)) | ||
Matt Mackall
|
r12041 | |||
Matt Mackall
|
r12043 | class unbundle10(object): | ||
def __init__(self, fh, alg): | ||||
Matt Mackall
|
r12329 | self._stream = decompressor(fh, alg) | ||
Matt Mackall
|
r12044 | self._type = alg | ||
Matt Mackall
|
r12334 | self.callback = None | ||
Matt Mackall
|
r12044 | def compressed(self): | ||
return self._type != 'UN' | ||||
Matt Mackall
|
r12043 | def read(self, l): | ||
return self._stream.read(l) | ||||
Matt Mackall
|
r12330 | def seek(self, pos): | ||
return self._stream.seek(pos) | ||||
def tell(self): | ||||
Matt Mackall
|
r12332 | return self._stream.tell() | ||
Matt Mackall
|
r12347 | def close(self): | ||
return self._stream.close() | ||||
Matt Mackall
|
r12334 | |||
def chunklength(self): | ||||
d = self.read(4) | ||||
if not d: | ||||
return 0 | ||||
l = max(0, struct.unpack(">l", d)[0] - 4) | ||||
if l and self.callback: | ||||
self.callback() | ||||
return l | ||||
Matt Mackall
|
r12333 | def chunk(self): | ||
Matt Mackall
|
r12334 | """return the next chunk from changegroup 'source' as a string""" | ||
l = self.chunklength() | ||||
d = self.read(l) | ||||
if len(d) < l: | ||||
raise util.Abort(_("premature EOF reading chunk" | ||||
" (got %d bytes, expected %d)") | ||||
% (len(d), l)) | ||||
return d | ||||
Matt Mackall
|
r12336 | def parsechunk(self): | ||
l = self.chunklength() | ||||
if not l: | ||||
return {} | ||||
h = self.read(80) | ||||
node, p1, p2, cs = struct.unpack("20s20s20s20s", h) | ||||
data = self.read(l - 80) | ||||
return dict(node=node, p1=p1, p2=p2, cs=cs, data=data) | ||||
Matt Mackall
|
r12329 | class headerlessfixup(object): | ||
def __init__(self, fh, h): | ||||
self._h = h | ||||
self._fh = fh | ||||
def read(self, n): | ||||
if self._h: | ||||
d, self._h = self._h[:n], self._h[n:] | ||||
if len(d) < n: | ||||
d += self._fh.read(n - len(d)) | ||||
return d | ||||
return self._fh.read(n) | ||||
Dirkjan Ochtman
|
r6154 | def readbundle(fh, fname): | ||
header = fh.read(6) | ||||
Matt Mackall
|
r12042 | |||
if not fname: | ||||
fname = "stream" | ||||
if not header.startswith('HG') and header.startswith('\0'): | ||||
Matt Mackall
|
r12329 | fh = headerlessfixup(fh, header) | ||
Matt Mackall
|
r12042 | header = "HG10UN" | ||
magic, version, alg = header[0:2], header[2:4], header[4:6] | ||||
if magic != 'HG': | ||||
raise util.Abort(_('%s: not a Mercurial bundle') % fname) | ||||
if version != '10': | ||||
raise util.Abort(_('%s: unknown bundle version %s') % (fname, version)) | ||||
Matt Mackall
|
r12043 | return unbundle10(fh, alg) | ||