proto.py
217 lines
| 7.5 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. | ||||
liscju
|
r29312 | from __future__ import absolute_import | ||
various
|
r15168 | |||
import os | ||||
from mercurial.i18n import _ | ||||
Gregory Szorc
|
r43355 | from mercurial.pycompat import open | ||
various
|
r15168 | |||
liscju
|
r29312 | from mercurial import ( | ||
error, | ||||
Matt Harbison
|
r41092 | exthelper, | ||
liscju
|
r29312 | httppeer, | ||
util, | ||||
Gregory Szorc
|
r36091 | wireprototypes, | ||
Gregory Szorc
|
r37632 | wireprotov1peer, | ||
Matt Harbison
|
r41092 | wireprotov1server, | ||
liscju
|
r29312 | ) | ||
Augie Fackler
|
r43346 | from . import lfutil | ||
liscju
|
r29312 | |||
timeless
|
r28883 | urlerr = util.urlerr | ||
urlreq = util.urlreq | ||||
Augie Fackler
|
r43346 | LARGEFILES_REQUIRED_MSG = ( | ||
Augie Fackler
|
r43347 | b'\nThis repository uses the largefiles extension.' | ||
b'\n\nPlease enable it in your Mercurial config ' | ||||
b'file.\n' | ||||
Augie Fackler
|
r43346 | ) | ||
various
|
r15168 | |||
Matt Harbison
|
r41092 | eh = exthelper.exthelper() | ||
Augie Fackler
|
r43346 | |||
various
|
r15168 | def putlfile(repo, proto, sha): | ||
Augie Fackler
|
r46554 | """Server command for putting a largefile into a repository's local store | ||
and into the user cache.""" | ||||
Gregory Szorc
|
r36084 | with proto.mayberedirectstdio() as output: | ||
path = lfutil.storepath(repo, sha) | ||||
util.makedirs(os.path.dirname(path)) | ||||
tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) | ||||
hlian
|
r16594 | |||
Gregory Szorc
|
r36084 | try: | ||
Joerg Sonnenberger
|
r37432 | for p in proto.getpayload(): | ||
tmpfp.write(p) | ||||
Gregory Szorc
|
r36084 | tmpfp._fp.seek(0) | ||
if sha != lfutil.hexsha1(tmpfp._fp): | ||||
Augie Fackler
|
r43347 | raise IOError(0, _(b'largefile contents do not match hash')) | ||
Gregory Szorc
|
r36084 | tmpfp.close() | ||
lfutil.linktousercache(repo, sha) | ||||
except IOError as e: | ||||
Augie Fackler
|
r43346 | repo.ui.warn( | ||
Augie Fackler
|
r43347 | _(b'largefiles: failed to put %s into store: %s\n') | ||
Augie Fackler
|
r43346 | % (sha, e.strerror) | ||
) | ||||
Gregory Szorc
|
r37309 | return wireprototypes.pushres( | ||
Augie Fackler
|
r43347 | 1, output.getvalue() if output else b'' | ||
Augie Fackler
|
r43346 | ) | ||
Gregory Szorc
|
r36084 | finally: | ||
tmpfp.discard() | ||||
various
|
r15168 | |||
Augie Fackler
|
r43347 | return wireprototypes.pushres(0, output.getvalue() if output else b'') | ||
various
|
r15168 | |||
Augie Fackler
|
r43346 | |||
various
|
r15168 | def getlfile(repo, proto, sha): | ||
Augie Fackler
|
r46554 | """Server command for retrieving a largefile from the repository-local | ||
cache or user cache.""" | ||||
various
|
r15168 | filename = lfutil.findfile(repo, sha) | ||
if not filename: | ||||
Augie Fackler
|
r43346 | raise error.Abort( | ||
Augie Fackler
|
r43347 | _(b'requested largefile %s not present in cache') % sha | ||
Augie Fackler
|
r43346 | ) | ||
Augie Fackler
|
r43347 | f = open(filename, b'rb') | ||
various
|
r15168 | 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(): | ||
Augie Fackler
|
r43347 | yield b'%d\n' % length | ||
Mads Kiilerich
|
r19009 | for chunk in util.filechunkiter(f): | ||
various
|
r15168 | yield chunk | ||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r37309 | return wireprototypes.streamreslegacy(gen=generator()) | ||
various
|
r15168 | |||
Augie Fackler
|
r43346 | |||
various
|
r15168 | def statlfile(repo, proto, sha): | ||
Augie Fackler
|
r46554 | """Server command for checking if a largefile is present - returns '2\n' if | ||
Mads Kiilerich
|
r28576 | the largefile is missing, '0\n' if it seems to be in good condition. | ||
Mads Kiilerich
|
r18488 | |||
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' | ||||
Augie Fackler
|
r46554 | server side.""" | ||
various
|
r15168 | filename = lfutil.findfile(repo, sha) | ||
if not filename: | ||||
Augie Fackler
|
r43347 | return wireprototypes.bytesresponse(b'2\n') | ||
return wireprototypes.bytesresponse(b'0\n') | ||||
various
|
r15168 | |||
Augie Fackler
|
r43346 | |||
various
|
r15168 | def wirereposetup(ui, repo): | ||
Joerg Sonnenberger
|
r46816 | orig_commandexecutor = repo.commandexecutor | ||
various
|
r15168 | class lfileswirerepository(repo.__class__): | ||
Joerg Sonnenberger
|
r46816 | def commandexecutor(self): | ||
executor = orig_commandexecutor() | ||||
if self.capable(b'largefiles'): | ||||
orig_callcommand = executor.callcommand | ||||
class lfscommandexecutor(executor.__class__): | ||||
def callcommand(self, command, args): | ||||
if command == b'heads': | ||||
command = b'lheads' | ||||
return orig_callcommand(command, args) | ||||
executor.__class__ = lfscommandexecutor | ||||
return executor | ||||
@wireprotov1peer.batchable | ||||
def lheads(self): | ||||
return self.heads.batchable(self) | ||||
various
|
r15168 | 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): | ||
Augie Fackler
|
r43346 | res = self._call( | ||
Augie Fackler
|
r43347 | b'putlfile', | ||
Augie Fackler
|
r43346 | data=fd, | ||
sha=sha, | ||||
Augie Fackler
|
r43906 | headers={'content-type': 'application/mercurial-0.1'}, | ||
Augie Fackler
|
r43346 | ) | ||
various
|
r15168 | try: | ||
Augie Fackler
|
r43347 | d, output = res.split(b'\n', 1) | ||
Kevin Gessner
|
r15778 | for l in output.splitlines(True): | ||
Augie Fackler
|
r43347 | self.ui.warn(_(b'remote: '), l) # assume l ends with \n | ||
Kevin Gessner
|
r15778 | return int(d) | ||
Mads Kiilerich
|
r26825 | except ValueError: | ||
Augie Fackler
|
r43347 | self.ui.warn(_(b'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: | ||||
Augie Fackler
|
r43347 | ret, output = self._callpush(b"putlfile", fd, sha=sha) | ||
if ret == b"": | ||||
raise error.ResponseError( | ||||
_(b'putlfile failed:'), output | ||||
) | ||||
various
|
r15168 | return int(ret) | ||
except IOError: | ||||
return 1 | ||||
except ValueError: | ||||
raise error.ResponseError( | ||||
Augie Fackler
|
r43347 | _(b'putlfile failed (unexpected response):'), ret | ||
Augie Fackler
|
r43346 | ) | ||
various
|
r15168 | |||
def getlfile(self, sha): | ||||
Mads Kiilerich
|
r19004 | """returns an iterable with the chunks of the file with sha sha""" | ||
Augie Fackler
|
r43347 | stream = self._callstream(b"getlfile", sha=sha) | ||
various
|
r15168 | length = stream.readline() | ||
try: | ||||
length = int(length) | ||||
except ValueError: | ||||
Augie Fackler
|
r43346 | self._abort( | ||
Augie Fackler
|
r43347 | error.ResponseError(_(b"unexpected response:"), length) | ||
Augie Fackler
|
r43346 | ) | ||
Mads Kiilerich
|
r19004 | |||
Mads Kiilerich
|
r19005 | # SSH streams will block if reading more than length | ||
Mads Kiilerich
|
r30181 | for chunk in util.filechunkiter(stream, limit=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: | ||||
Augie Fackler
|
r43346 | self._abort( | ||
Augie Fackler
|
r43347 | error.ResponseError(_(b"unexpected response:"), chunk) | ||
Augie Fackler
|
r43346 | ) | ||
various
|
r15168 | |||
Gregory Szorc
|
r37632 | @wireprotov1peer.batchable | ||
various
|
r15168 | def statlfile(self, sha): | ||
Gregory Szorc
|
r37632 | f = wireprotov1peer.future() | ||
Augie Fackler
|
r43347 | result = {b'sha': sha} | ||
Na'Tosha Bard
|
r17127 | yield result, f | ||
various
|
r15168 | try: | ||
Na'Tosha Bard
|
r17127 | yield int(f.value) | ||
timeless
|
r28883 | except (ValueError, urlerr.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 | ||||
Augie Fackler
|
r43346 | |||
various
|
r15168 | # advertise the largefiles=serve capability | ||
Augie Fackler
|
r43347 | @eh.wrapfunction(wireprotov1server, b'_capabilities') | ||
Matt Harbison
|
r35523 | def _capabilities(orig, repo, proto): | ||
'''announce largefile server capability''' | ||||
caps = orig(repo, proto) | ||||
Augie Fackler
|
r43347 | caps.append(b'largefiles=serve') | ||
Matt Harbison
|
r35523 | return caps | ||
various
|
r15168 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r37502 | def heads(orig, repo, proto): | ||
Augie Fackler
|
r46554 | """Wrap server command - largefile capable clients will know to call | ||
lheads instead""" | ||||
various
|
r15168 | if lfutil.islfilesrepo(repo): | ||
Gregory Szorc
|
r37309 | return wireprototypes.ooberror(LARGEFILES_REQUIRED_MSG) | ||
Gregory Szorc
|
r37502 | |||
return orig(repo, proto) | ||||