3 final class DiffusionGitUploadPackWireProtocol
4 extends DiffusionGitWireProtocol
{
6 private $readMode = 'length';
8 private $readFrameLength;
9 private $readFrames = array();
11 private $readFrameMode = 'refs';
12 private $refFrames = array();
14 private $readMessages = array();
16 public function willReadBytes($bytes) {
17 if ($this->readBuffer
=== null) {
18 $this->readBuffer
= new PhutilRope();
20 $buffer = $this->readBuffer
;
22 $buffer->append($bytes);
25 $len = $buffer->getByteLength();
26 switch ($this->readMode
) {
28 // We're expecting 4 bytes containing the length of the protocol
29 // frame as hexadecimal in ASCII text, like "01ab". Wait until we
30 // see at least 4 bytes on the wire.
33 $bytes = $this->peekBytes($len);
34 if (!preg_match('/^[0-9a-f]+\z/', $bytes)) {
37 'Bad frame length character in Git protocol ("%s"), '.
38 'expected a 4-digit hexadecimal value encoded as ASCII '.
44 // We can't make any more progress until we get enough bytes, so
45 // we're done with state processing.
49 $frame_length = $this->readBytes(4);
50 $frame_length = hexdec($frame_length);
52 // Note that the frame length includes the 4 header bytes, so we
53 // usually expect a length of 5 or larger. Frames with length 0
55 if ($frame_length === 0) {
56 $this->readFrames
[] = $this->newProtocolFrame('null', '');
57 } else if ($frame_length >= 1 && $frame_length <= 3) {
60 'Encountered Git protocol frame with unexpected frame '.
64 $this->readFrameLength
= $frame_length - 4;
65 $this->readMode
= 'frame';
70 // We're expecting a protocol frame of a specified length. Note that
71 // it is possible for a frame to have length 0.
73 // We don't have enough bytes yet, so wait for more.
74 if ($len < $this->readFrameLength
) {
78 if ($this->readFrameLength
> 0) {
79 $bytes = $this->readBytes($this->readFrameLength
);
84 // Emit a protocol frame.
85 $this->readFrames
[] = $this->newProtocolFrame('data', $bytes);
86 $this->readMode
= 'length';
92 switch ($this->readFrameMode
) {
94 if (!$this->readFrames
) {
98 foreach ($this->readFrames
as $key => $frame) {
99 unset($this->readFrames
[$key]);
101 if ($frame['type'] === 'null') {
102 $ref_frames = $this->refFrames
;
103 $this->refFrames
= array();
105 $ref_frames[] = $frame;
107 $this->readMessages
[] = $this->newProtocolRefMessage($ref_frames);
108 $this->readFrameMode
= 'passthru';
111 $this->refFrames
[] = $frame;
117 if (!$this->readFrames
) {
121 $this->readMessages
[] = $this->newProtocolDataMessage(
123 $this->readFrames
= array();
130 foreach ($this->readMessages
as $key => $message) {
132 unset($this->readMessages
[$key]);
134 $wire = implode('', $wire);
139 public function willWriteBytes($bytes) {
143 private function readBytes($count) {
144 $buffer = $this->readBuffer
;
146 $bytes = $buffer->getPrefixBytes($count);
147 $buffer->removeBytesFromHead($count);
152 private function peekBytes($count) {
153 $buffer = $this->readBuffer
;
154 return $buffer->getPrefixBytes($count);
157 private function newProtocolFrame($type, $bytes) {
160 'length' => strlen($bytes),
165 private function newProtocolRefMessage(array $frames) {
166 $head_key = head_key($frames);
167 $last_key = last_key($frames);
169 $capabilities = null;
173 foreach ($frames as $key => $frame) {
174 $is_last = ($key === $last_key);
176 // This is a "0000" frame at the end of the list of refs, so we pass
177 // it through unmodified after we figure out what the rest of the
178 // frames should look like, below.
179 $last_frame = $frame;
183 $is_first = ($key === $head_key);
185 // Otherwise, we expect a list of:
187 // <hash> <ref-name>\0<capabilities>
191 // See T13309. The end of this list (which may be empty if a repository
192 // does not have any refs) has a list of zero or more of these:
196 // These entries are present if the repository is a shallow clone
197 // which was made with the "--depth" flag.
199 // Note that "shallow" frames do not advertise capabilities, and if
200 // a repository has only "shallow" frames, capabilities are never
203 $bytes = $frame['bytes'];
206 $capabilities_pattern = '\0(?P<capabilities>[^\n]+)';
208 $capabilities_pattern = '';
215 '(?P<hash>[0-9a-f]{40}) (?P<name>[^\0\n]+)'.$capabilities_pattern.
217 'shallow (?P<shallow>[0-9a-f]{40})'.
229 'Unexpected "git upload-pack" initial protocol frame: expected '.
230 '"<hash> <name>\0<capabilities>\n", or '.
231 '"shallow <hash>\n", got "%s".',
236 'Unexpected "git upload-pack" protocol frame: expected '.
237 '"<hash> <name>\n", or "shallow <hash>\n", got "%s".',
242 if (isset($matches['shallow'])) {
244 $hash = $matches['shallow'];
247 $name = $matches['name'];
248 $hash = $matches['hash'];
252 if (isset($matches['capabilities'])) {
253 $capabilities = $matches['capabilities'];
259 'shallow' => $is_shallow,
263 $capabilities = DiffusionGitWireProtocolCapabilities
::newFromWireFormat(
266 $ref_list = id(new DiffusionGitWireProtocolRefList())
267 ->setCapabilities($capabilities);
269 foreach ($refs as $ref) {
270 $wire_ref = id(new DiffusionGitWireProtocolRef())
271 ->setHash($ref['hash']);
273 if ($ref['shallow']) {
274 $wire_ref->setIsShallow(true);
276 $wire_ref->setName($ref['name']);
279 $ref_list->addRef($wire_ref);
282 // TODO: Here, we have a structured list of refs. In a future change,
283 // we are free to mutate the structure before flattening it back into
286 $refs = $ref_list->getRefs();
288 // Before we write the ref list, sort it for consistency with native
289 // Git output. We may have added, removed, or renamed refs and ended up
290 // with an out-of-order list.
292 $refs = msortv($refs, 'newSortVector');
294 // The first ref we send back includes the capabilities data. Note that if
295 // we send back no refs, we also don't send back capabilities! This is
296 // a little surprising, but is consistent with the native behavior of the
299 // Likewise, we don't send back any capabilities if we're sending only
304 foreach ($refs as $ref) {
305 $is_shallow = $ref->getIsShallow();
311 } else if ($is_first) {
316 $ref_list->getCapabilities()->toWireFormat());
324 $output[] = $this->newProtocolFrame('data', $result);
328 $output[] = $last_frame;
330 return $this->newProtocolDataMessage($output);
333 private function newProtocolDataMessage(array $frames) {
336 foreach ($frames as $frame) {
337 switch ($frame['type']) {
342 $message[] = sprintf(
344 $frame['length'] +
4,
350 $message = implode('', $message);