1 # protocol.py -- Shared parts of the git protocols
2 # Copyright (C) 2008 John Carr <john.carr@unrouted.co.uk>
3 # Copyright (C) 2008 Jelmer Vernooij <jelmer@samba.org>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
8 # or (at your option) any later version of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 """Generic functions for talking the git smart server protocol."""
22 from cStringIO import StringIO
26 from dulwich.errors import (
30 from dulwich.misc import (
40 MULTI_ACK_DETAILED = 2
43 class ProtocolFile(object):
45 Some network ops are like file ops. The file ops expect to operate on
46 file objects, so provide them with a dummy file.
49 def __init__(self, read, write):
60 class Protocol(object):
62 def __init__(self, read, write, report_activity=None):
65 self.report_activity = report_activity
67 def read_pkt_line(self):
69 Reads a 'pkt line' from the remote git process
71 :return: The next string from the stream
74 sizestr = self.read(4)
76 raise HangupException()
77 size = int(sizestr, 16)
79 if self.report_activity:
80 self.report_activity(4, 'read')
82 if self.report_activity:
83 self.report_activity(size, 'read')
84 return self.read(size-4)
85 except socket.error, e:
86 raise GitProtocolError(e)
88 def read_pkt_seq(self):
89 pkt = self.read_pkt_line()
92 pkt = self.read_pkt_line()
94 def write_pkt_line(self, line):
96 Sends a 'pkt line' to the remote git process
98 :param line: A string containing the data to send
103 if self.report_activity:
104 self.report_activity(4, 'write')
106 self.write("%04x%s" % (len(line)+4, line))
107 if self.report_activity:
108 self.report_activity(4+len(line), 'write')
109 except socket.error, e:
110 raise GitProtocolError(e)
112 def write_file(self):
113 class ProtocolFile(object):
115 def __init__(self, proto):
119 def write(self, data):
120 self._proto.write(data)
121 self._offset += len(data)
129 return ProtocolFile(self)
131 def write_sideband(self, channel, blob):
133 Write data to the sideband (a git multiplexing method)
135 :param channel: int specifying which channel to write to
136 :param blob: a blob of data (as a string) to send on this channel
138 # a pktline can be a max of 65520. a sideband line can therefore be
140 # WTF: Why have the len in ASCII, but the channel in binary.
142 self.write_pkt_line("%s%s" % (chr(channel), blob[:65515]))
145 def send_cmd(self, cmd, *args):
147 Send a command and some arguments to a git server
151 :param cmd: The remote service to access
152 :param args: List of arguments to send to remove service
154 self.write_pkt_line("%s %s" % (cmd, "".join(["%s\0" % a for a in args])))
158 Read a command and some arguments from the git client
162 :return: A tuple of (command, [list of arguments])
164 line = self.read_pkt_line()
165 splice_at = line.find(" ")
166 cmd, args = line[:splice_at], line[splice_at+1:]
167 assert args[-1] == "\x00"
168 return cmd, args[:-1].split(chr(0))
171 _RBUFSIZE = 8192 # Default read buffer size.
174 class ReceivableProtocol(Protocol):
175 """Variant of Protocol that allows reading up to a size without blocking.
177 This class has a recv() method that behaves like socket.recv() in addition
180 If you want to read n bytes from the wire and block until exactly n bytes
181 (or EOF) are read, use read(n). If you want to read at most n bytes from the
182 wire but don't care if you get less, use recv(n). Note that recv(n) will
183 still block until at least one byte is read.
186 def __init__(self, recv, write, report_activity=None, rbufsize=_RBUFSIZE):
187 super(ReceivableProtocol, self).__init__(self.read, write,
190 self._rbuf = StringIO()
191 self._rbufsize = rbufsize
193 def read(self, size):
194 # From _fileobj.read in socket.py in the Python 2.6.5 standard library,
195 # with the following modifications:
196 # - omit the size <= 0 branch
197 # - seek back to start rather than 0 in case some buffer has been
199 # - use SEEK_END instead of the magic number.
200 # Copyright (c) 2001-2010 Python Software Foundation; All Rights Reserved
201 # Licensed under the Python Software Foundation License.
202 # TODO: see if buffer is more efficient than cStringIO.
205 # Our use of StringIO rather than lists of string objects returned by
206 # recv() minimizes memory usage and fragmentation that occurs when
207 # rbufsize is large compared to the typical return value of recv().
210 buf.seek(0, SEEK_END)
211 # buffer may have been partially consumed by recv()
212 buf_len = buf.tell() - start
214 # Already have size bytes in our buffer? Extract and return.
217 self._rbuf = StringIO()
218 self._rbuf.write(buf.read())
222 self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
224 left = size - buf_len
225 # recv() will malloc the amount of memory given as its
226 # parameter even though it often returns much less data
227 # than that. The returned data string is short lived
228 # as we copy it into a StringIO and free it. This avoids
229 # fragmentation issues on many platforms.
230 data = self._recv(left)
234 if n == size and not buf_len:
235 # Shortcut. Avoid buffer data copies when:
236 # - We have no data in our buffer.
238 # - Our call to recv returned exactly the
239 # number of bytes we were asked to read.
243 del data # explicit free
245 assert n <= left, "_recv(%d) returned %d bytes" % (left, n)
248 del data # explicit free
249 #assert buf_len == buf.tell()
253 def recv(self, size):
258 buf.seek(0, SEEK_END)
262 left = buf_len - start
264 # only read from the wire if our read buffer is exhausted
265 data = self._recv(self._rbufsize)
266 if len(data) == size:
267 # shortcut: skip the buffer if we read exactly size bytes
272 del data # explicit free
274 return buf.read(size)
277 def extract_capabilities(text):
278 """Extract a capabilities list from a string, if present.
280 :param text: String to extract from
281 :return: Tuple with text with capabilities removed and list of capabilities
285 text, capabilities = text.rstrip().split("\0")
286 return (text, capabilities.strip().split(" "))
289 def extract_want_line_capabilities(text):
290 """Extract a capabilities list from a want line, if present.
292 Note that want lines have capabilities separated from the rest of the line
293 by a space instead of a null byte. Thus want lines have the form:
295 want obj-id cap1 cap2 ...
297 :param text: Want line to extract from
298 :return: Tuple with text with capabilities removed and list of capabilities
300 split_text = text.rstrip().split(" ")
301 if len(split_text) < 3:
303 return (" ".join(split_text[:2]), split_text[2:])
306 def ack_type(capabilities):
307 """Extract the ack type from a capabilities list."""
308 if 'multi_ack_detailed' in capabilities:
309 return MULTI_ACK_DETAILED
310 elif 'multi_ack' in capabilities: