proto.py
175 lines
| 6.6 KiB
| text/x-python
|
PythonLexer
various
|
r15168 | # Copyright 2011 Fog Creek Software | ||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
import os | ||||
import urllib2 | ||||
Mads Kiilerich
|
r19917 | import re | ||
various
|
r15168 | |||
Peter Arrenbrecht
|
r17192 | from mercurial import error, httppeer, util, wireproto | ||
various
|
r15168 | from mercurial.i18n import _ | ||
import lfutil | ||||
Greg Ward
|
r15255 | LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.' | ||
'\n\nPlease enable it in your Mercurial config ' | ||||
'file.\n') | ||||
various
|
r15168 | |||
Bryan O'Sullivan
|
r18922 | # these will all be replaced by largefiles.uisetup | ||
capabilitiesorig = None | ||||
ssholdcallstream = None | ||||
httpoldcallstream = None | ||||
various
|
r15168 | def putlfile(repo, proto, sha): | ||
Benjamin Pollack
|
r15317 | '''Put a largefile into a repository's local store and into the | ||
user cache.''' | ||||
various
|
r15168 | proto.redirect() | ||
Hao Lian
|
r15391 | |||
hlian
|
r16594 | path = lfutil.storepath(repo, sha) | ||
util.makedirs(os.path.dirname(path)) | ||||
tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) | ||||
various
|
r15168 | try: | ||
Matt Mackall
|
r25079 | proto.getfile(tmpfp) | ||
tmpfp._fp.seek(0) | ||||
if sha != lfutil.hexsha1(tmpfp._fp): | ||||
raise IOError(0, _('largefile contents do not match hash')) | ||||
tmpfp.close() | ||||
lfutil.linktousercache(repo, sha) | ||||
Gregory Szorc
|
r25660 | except IOError as e: | ||
Matt Mackall
|
r25079 | repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') % | ||
(sha, e.strerror)) | ||||
return wireproto.pushres(1) | ||||
various
|
r15168 | finally: | ||
Martin Geisler
|
r16155 | tmpfp.discard() | ||
various
|
r15168 | |||
return wireproto.pushres(0) | ||||
def getlfile(repo, proto, sha): | ||||
Greg Ward
|
r15252 | '''Retrieve a largefile from the repository-local cache or system | ||
cache.''' | ||||
various
|
r15168 | filename = lfutil.findfile(repo, sha) | ||
if not filename: | ||||
raise util.Abort(_('requested largefile %s not present in cache') % sha) | ||||
f = open(filename, 'rb') | ||||
length = os.fstat(f.fileno())[6] | ||||
Greg Ward
|
r15252 | |||
# Since we can't set an HTTP content-length header here, and | ||||
# Mercurial core provides no way to give the length of a streamres | ||||
# (and reading the entire file into RAM would be ill-advised), we | ||||
# just send the length on the first line of the response, like the | ||||
# ssh proto does for string responses. | ||||
various
|
r15168 | def generator(): | ||
yield '%d\n' % length | ||||
Mads Kiilerich
|
r19009 | for chunk in util.filechunkiter(f): | ||
various
|
r15168 | yield chunk | ||
return wireproto.streamres(generator()) | ||||
def statlfile(repo, proto, sha): | ||||
Mads Kiilerich
|
r18488 | '''Return '2\n' if the largefile is missing, '0\n' if it seems to be in | ||
good condition. | ||||
The value 1 is reserved for mismatched checksum, but that is too expensive | ||||
to be verified on every stat and must be caught be running 'hg verify' | ||||
server side.''' | ||||
various
|
r15168 | filename = lfutil.findfile(repo, sha) | ||
if not filename: | ||||
return '2\n' | ||||
Mads Kiilerich
|
r18488 | return '0\n' | ||
various
|
r15168 | |||
def wirereposetup(ui, repo): | ||||
class lfileswirerepository(repo.__class__): | ||||
def putlfile(self, sha, fd): | ||||
# unfortunately, httprepository._callpush tries to convert its | ||||
# input file-like into a bundle before sending it, so we can't use | ||||
# it ... | ||||
Peter Arrenbrecht
|
r17192 | if issubclass(self.__class__, httppeer.httppeer): | ||
Kevin Gessner
|
r15778 | res = None | ||
various
|
r15168 | try: | ||
Kevin Gessner
|
r15778 | res = self._call('putlfile', data=fd, sha=sha, | ||
headers={'content-type':'application/mercurial-0.1'}) | ||||
d, output = res.split('\n', 1) | ||||
for l in output.splitlines(True): | ||||
Mads Kiilerich
|
r19949 | self.ui.warn(_('remote: '), l) # assume l ends with \n | ||
Kevin Gessner
|
r15778 | return int(d) | ||
various
|
r15168 | except (ValueError, urllib2.HTTPError): | ||
Mads Kiilerich
|
r19947 | self.ui.warn(_('unexpected putlfile response: %r\n') % res) | ||
various
|
r15168 | return 1 | ||
# ... but we can't use sshrepository._call because the data= | ||||
# argument won't get sent, and _callpush does exactly what we want | ||||
# in this case: send the data straight through | ||||
else: | ||||
try: | ||||
ret, output = self._callpush("putlfile", fd, sha=sha) | ||||
if ret == "": | ||||
raise error.ResponseError(_('putlfile failed:'), | ||||
output) | ||||
return int(ret) | ||||
except IOError: | ||||
return 1 | ||||
except ValueError: | ||||
raise error.ResponseError( | ||||
_('putlfile failed (unexpected response):'), ret) | ||||
def getlfile(self, sha): | ||||
Mads Kiilerich
|
r19004 | """returns an iterable with the chunks of the file with sha sha""" | ||
various
|
r15168 | stream = self._callstream("getlfile", sha=sha) | ||
length = stream.readline() | ||||
try: | ||||
length = int(length) | ||||
except ValueError: | ||||
Matt Mackall
|
r15170 | self._abort(error.ResponseError(_("unexpected response:"), | ||
length)) | ||||
Mads Kiilerich
|
r19004 | |||
Mads Kiilerich
|
r19005 | # SSH streams will block if reading more than length | ||
for chunk in util.filechunkiter(stream, 128 * 1024, length): | ||||
Mads Kiilerich
|
r19004 | yield chunk | ||
Mads Kiilerich
|
r19006 | # HTTP streams must hit the end to process the last empty | ||
# chunk of Chunked-Encoding so the connection can be reused. | ||||
if issubclass(self.__class__, httppeer.httppeer): | ||||
chunk = stream.read(1) | ||||
if chunk: | ||||
self._abort(error.ResponseError(_("unexpected response:"), | ||||
chunk)) | ||||
various
|
r15168 | |||
Mads Kiilerich
|
r21084 | @wireproto.batchable | ||
various
|
r15168 | def statlfile(self, sha): | ||
Mads Kiilerich
|
r21084 | f = wireproto.future() | ||
Na'Tosha Bard
|
r17127 | result = {'sha': sha} | ||
yield result, f | ||||
various
|
r15168 | try: | ||
Na'Tosha Bard
|
r17127 | yield int(f.value) | ||
various
|
r15168 | except (ValueError, urllib2.HTTPError): | ||
Greg Ward
|
r15252 | # If the server returns anything but an integer followed by a | ||
various
|
r15168 | # newline, newline, it's not speaking our language; if we get | ||
# an HTTP error, we can't be sure the largefile is present; | ||||
Greg Ward
|
r15252 | # either way, consider it missing. | ||
Na'Tosha Bard
|
r17127 | yield 2 | ||
various
|
r15168 | |||
repo.__class__ = lfileswirerepository | ||||
# advertise the largefiles=serve capability | ||||
def capabilities(repo, proto): | ||||
Na'Tosha Bard
|
r16247 | return capabilitiesorig(repo, proto) + ' largefiles=serve' | ||
various
|
r15168 | |||
def heads(repo, proto): | ||||
if lfutil.islfilesrepo(repo): | ||||
Na'Tosha Bard
|
r15224 | return wireproto.ooberror(LARGEFILES_REQUIRED_MSG) | ||
various
|
r15168 | return wireproto.heads(repo, proto) | ||
Na'Tosha Bard
|
r16247 | def sshrepocallstream(self, cmd, **args): | ||
various
|
r15168 | if cmd == 'heads' and self.capable('largefiles'): | ||
cmd = 'lheads' | ||||
if cmd == 'batch' and self.capable('largefiles'): | ||||
args['cmds'] = args['cmds'].replace('heads ', 'lheads ') | ||||
Na'Tosha Bard
|
r16247 | return ssholdcallstream(self, cmd, **args) | ||
various
|
r15168 | |||
Mads Kiilerich
|
r19917 | headsre = re.compile(r'(^|;)heads\b') | ||
Na'Tosha Bard
|
r16247 | def httprepocallstream(self, cmd, **args): | ||
various
|
r15168 | if cmd == 'heads' and self.capable('largefiles'): | ||
cmd = 'lheads' | ||||
if cmd == 'batch' and self.capable('largefiles'): | ||||
Mads Kiilerich
|
r19917 | args['cmds'] = headsre.sub('lheads', args['cmds']) | ||
Na'Tosha Bard
|
r16247 | return httpoldcallstream(self, cmd, **args) | ||