Something went wrong. Try again.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
Something went wrong. Try again.
9.8 kB · 355 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356<?php
final class DiffusionGitUploadPackWireProtocol extends DiffusionGitWireProtocol {
private $readMode = 'length'; private $readBuffer; private $readFrameLength; private $readFrames = array();
private $readFrameMode = 'refs'; private $refFrames = array();
private $readMessages = array();
public function willReadBytes($bytes) { if ($this->readBuffer === null) { $this->readBuffer = new PhutilRope(); } $buffer = $this->readBuffer;
$buffer->append($bytes);
while (true) { $len = $buffer->getByteLength(); switch ($this->readMode) { case 'length': // We're expecting 4 bytes containing the length of the protocol // frame as hexadecimal in ASCII text, like "01ab". Wait until we // see at least 4 bytes on the wire. if ($len < 4) { if ($len > 0) { $bytes = $this->peekBytes($len); if (!preg_match('/^[0-9a-f]+\z/', $bytes)) { throw new Exception( pht( 'Bad frame length character in Git protocol ("%s"), '. 'expected a 4-digit hexadecimal value encoded as ASCII '. 'text.', $bytes)); } }
// We can't make any more progress until we get enough bytes, so // we're done with state processing. break 2; }
$frame_length = $this->readBytes(4); $frame_length = hexdec($frame_length);
// Note that the frame length includes the 4 header bytes, so we // usually expect a length of 5 or larger. Frames with length 0 // are boundaries. if ($frame_length === 0) { $this->readFrames[] = $this->newProtocolFrame('null', ''); } else if ($frame_length >= 1 && $frame_length <= 3) { throw new Exception( pht( 'Encountered Git protocol frame with unexpected frame '. 'length (%s)!', $frame_length)); } else { $this->readFrameLength = $frame_length - 4; $this->readMode = 'frame'; }
break; case 'frame': // We're expecting a protocol frame of a specified length. Note that // it is possible for a frame to have length 0.
// We don't have enough bytes yet, so wait for more. if ($len < $this->readFrameLength) { break 2; }
if ($this->readFrameLength > 0) { $bytes = $this->readBytes($this->readFrameLength); } else { $bytes = ''; }
// Emit a protocol frame. $this->readFrames[] = $this->newProtocolFrame('data', $bytes); $this->readMode = 'length'; break; } }
while (true) { switch ($this->readFrameMode) { case 'refs': if (!$this->readFrames) { break 2; }
foreach ($this->readFrames as $key => $frame) { unset($this->readFrames[$key]);
if ($frame['type'] === 'null') { $ref_frames = $this->refFrames; $this->refFrames = array();
$ref_frames[] = $frame;
$this->readMessages[] = $this->newProtocolRefMessage($ref_frames); $this->readFrameMode = 'passthru'; break; } else { $this->refFrames[] = $frame; } }
break; case 'passthru': if (!$this->readFrames) { break 2; }
$this->readMessages[] = $this->newProtocolDataMessage( $this->readFrames); $this->readFrames = array();
break; } }
$wire = array(); foreach ($this->readMessages as $key => $message) { $wire[] = $message; unset($this->readMessages[$key]); } $wire = implode('', $wire);
return $wire; }
public function willWriteBytes($bytes) { return $bytes; }
private function readBytes($count) { $buffer = $this->readBuffer;
$bytes = $buffer->getPrefixBytes($count); $buffer->removeBytesFromHead($count);
return $bytes; }
private function peekBytes($count) { $buffer = $this->readBuffer; return $buffer->getPrefixBytes($count); }
private function newProtocolFrame($type, $bytes) { return array( 'type' => $type, 'length' => strlen($bytes), 'bytes' => $bytes, ); }
private function newProtocolRefMessage(array $frames) { $head_key = head_key($frames); $last_key = last_key($frames);
$capabilities = null; $last_frame = null;
$refs = array(); foreach ($frames as $key => $frame) { $is_last = ($key === $last_key); if ($is_last) { // This is a "0000" frame at the end of the list of refs, so we pass // it through unmodified after we figure out what the rest of the // frames should look like, below. $last_frame = $frame; continue; }
$is_first = ($key === $head_key);
// Otherwise, we expect a list of: // // <hash> <ref-name>\0<capabilities> // <hash> <ref-name> // ... // // See T13309. The end of this list (which may be empty if a repository // does not have any refs) has a list of zero or more of these: // // shallow <hash> // // These entries are present if the repository is a shallow clone // which was made with the "--depth" flag. // // Note that "shallow" frames do not advertise capabilities, and if // a repository has only "shallow" frames, capabilities are never // advertised.
$bytes = $frame['bytes']; $matches = array(); if ($is_first) { $capabilities_pattern = '\0(?P<capabilities>[^\n]+)'; } else { $capabilities_pattern = ''; }
$ok = preg_match( '('. '^'. '(?:'. '(?P<hash>[0-9a-f]{40}) (?P<name>[^\0\n]+)'.$capabilities_pattern. '|'. 'shallow (?P<shallow>[0-9a-f]{40})'. ')'. '\n'. '\z'. ')', $bytes, $matches);
if (!$ok) { if ($is_first) { throw new Exception( pht( 'Unexpected "git upload-pack" initial protocol frame: expected '. '"<hash> <name>\0<capabilities>\n", or '. '"shallow <hash>\n", got "%s".', $bytes)); } else { throw new Exception( pht( 'Unexpected "git upload-pack" protocol frame: expected '. '"<hash> <name>\n", or "shallow <hash>\n", got "%s".', $bytes)); } }
if (isset($matches['shallow'])) { $name = null; $hash = $matches['shallow']; $is_shallow = true; } else { $name = $matches['name']; $hash = $matches['hash']; $is_shallow = false; }
if (isset($matches['capabilities'])) { $capabilities = $matches['capabilities']; }
$refs[] = array( 'hash' => $hash, 'name' => $name, 'shallow' => $is_shallow, ); }
$capabilities = DiffusionGitWireProtocolCapabilities::newFromWireFormat( $capabilities);
$ref_list = id(new DiffusionGitWireProtocolRefList()) ->setCapabilities($capabilities);
foreach ($refs as $ref) { $wire_ref = id(new DiffusionGitWireProtocolRef()) ->setHash($ref['hash']);
if ($ref['shallow']) { $wire_ref->setIsShallow(true); } else { $wire_ref->setName($ref['name']); }
$ref_list->addRef($wire_ref); }
// TODO: Here, we have a structured list of refs. In a future change, // we are free to mutate the structure before flattening it back into // wire format.
$refs = $ref_list->getRefs();
// Before we write the ref list, sort it for consistency with native // Git output. We may have added, removed, or renamed refs and ended up // with an out-of-order list.
$refs = msortv($refs, 'newSortVector');
// The first ref we send back includes the capabilities data. Note that if // we send back no refs, we also don't send back capabilities! This is // a little surprising, but is consistent with the native behavior of the // protocol.
// Likewise, we don't send back any capabilities if we're sending only // "shallow" frames.
$output = array(); $is_first = true; foreach ($refs as $ref) { $is_shallow = $ref->getIsShallow();
if ($is_shallow) { $result = sprintf( "shallow %s\n", $ref->getHash()); } else if ($is_first) { $result = sprintf( "%s %s\0%s\n", $ref->getHash(), $ref->getName(), $ref_list->getCapabilities()->toWireFormat()); } else { $result = sprintf( "%s %s\n", $ref->getHash(), $ref->getName()); }
$output[] = $this->newProtocolFrame('data', $result); $is_first = false; }
$output[] = $last_frame;
return $this->newProtocolDataMessage($output); }
private function newProtocolDataMessage(array $frames) { $message = array();
foreach ($frames as $frame) { switch ($frame['type']) { case 'null': $message[] = '0000'; break; case 'data': $message[] = sprintf( '%04x%s', $frame['length'] + 4, $frame['bytes']); break; } }
$message = implode('', $message);
return $message; }
}