Page MenuHomePhabricator

D20381.id.diff
No OneTemporary

D20381.id.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -813,6 +813,8 @@
'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php',
'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php',
'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php',
+ 'DiffusionGitUploadPackWireProtocol' => 'applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php',
+ 'DiffusionGitWireProtocol' => 'applications/diffusion/protocol/DiffusionGitWireProtocol.php',
'DiffusionGraphController' => 'applications/diffusion/controller/DiffusionGraphController.php',
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php',
@@ -6460,6 +6462,8 @@
'DiffusionRepositoryClusterEngineLogInterface',
),
'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow',
+ 'DiffusionGitUploadPackWireProtocol' => 'DiffusionGitWireProtocol',
+ 'DiffusionGitWireProtocol' => 'Phobject',
'DiffusionGraphController' => 'DiffusionController',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryListView' => 'DiffusionHistoryView',
diff --git a/src/applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php b/src/applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php
@@ -0,0 +1,335 @@
+<?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);
+
+ $output = array();
+ foreach ($frames as $key => $frame) {
+ $is_last = ($key === $last_key);
+ if ($is_last) {
+ $output[] = $frame;
+ // This is a "0000" frame at the end of the list of refs, so we pass
+ // it through unmodified.
+ continue;
+ }
+
+ $is_first = ($key === $head_key);
+
+ // Otherwise, we expect a list of:
+ //
+ // <hash> <ref-name>\0<capabilities>
+ // <hash> <ref-name>
+ // ...
+
+ $bytes = $frame['bytes'];
+ $matches = array();
+ if ($is_first) {
+ $ok = preg_match(
+ '('.
+ '^'.
+ '(?P<hash>[0-9a-f]{40})'.
+ ' '.
+ '(?P<name>[^\0\n]+)'.
+ '\0'.
+ '(?P<capabilities>[^\n]+)'.
+ '\n'.
+ '\z'.
+ ')',
+ $bytes,
+ $matches);
+ if (!$ok) {
+ throw new Exception(
+ pht(
+ 'Unexpected "git upload-pack" initial protocol frame: expected '.
+ '"<hash> <name>\0<capabilities>\n", got "%s".',
+ $bytes));
+ }
+ } else {
+ $ok = preg_match(
+ '('.
+ '^'.
+ '(?P<hash>[0-9a-f]{40})'.
+ ' '.
+ '(?P<name>[^\0\n]+)'.
+ '\n'.
+ '\z'.
+ ')',
+ $bytes,
+ $matches);
+ if (!$ok) {
+ throw new Exception(
+ pht(
+ 'Unexpected "git upload-pack" protocol frame: expected '.
+ '"<hash> <name>\n", got "%s".',
+ $bytes));
+ }
+ }
+
+ $hash = $matches['hash'];
+ $name = $matches['name'];
+ $capabilities = idx($matches, 'capabilities');
+
+ $ref = array(
+ 'hash' => $hash,
+ 'name' => $name,
+ 'capabilities' => $capabilities,
+ );
+
+ $old_ref = $ref;
+
+ $ref = $this->willReadRef($ref);
+
+ $new_ref = $ref;
+
+ $this->didRewriteRef($old_ref, $new_ref);
+
+ if ($ref === null) {
+ continue;
+ }
+
+ if (isset($ref['capabilities'])) {
+ $result = sprintf(
+ "%s %s\0%s\n",
+ $ref['hash'],
+ $ref['name'],
+ $ref['capabilities']);
+ } else {
+ $result = sprintf(
+ "%s %s\n",
+ $ref['hash'],
+ $ref['name']);
+ }
+
+ $output[] = $this->newProtocolFrame('data', $result);
+ }
+
+ 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;
+ }
+
+ private function willReadRef(array $ref) {
+ return $ref;
+ }
+
+ private function didRewriteRef($old_ref, $new_ref) {
+ $log = $this->getProtocolLog();
+ if (!$log) {
+ return;
+ }
+
+ if (!$old_ref) {
+ $old_name = null;
+ } else {
+ $old_name = $old_ref['name'];
+ }
+
+ if (!$new_ref) {
+ $new_name = null;
+ } else {
+ $new_name = $new_ref['name'];
+ }
+
+ if ($old_name === $new_name) {
+ return;
+ }
+
+ if ($old_name === null) {
+ $old_name = '<null>';
+ }
+
+ if ($new_name === null) {
+ $new_name = '<null>';
+ }
+
+ $log->didWriteFrame(
+ pht(
+ 'Rewrite Ref: %s -> %s',
+ $old_name,
+ $new_name));
+ }
+
+}
diff --git a/src/applications/diffusion/protocol/DiffusionGitWireProtocol.php b/src/applications/diffusion/protocol/DiffusionGitWireProtocol.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diffusion/protocol/DiffusionGitWireProtocol.php
@@ -0,0 +1,19 @@
+<?php
+
+abstract class DiffusionGitWireProtocol extends Phobject {
+
+ private $protocolLog;
+
+ final public function setProtocolLog(PhabricatorProtocolLog $protocol_log) {
+ $this->protocolLog = $protocol_log;
+ return $this;
+ }
+
+ final public function getProtocolLog() {
+ return $this->protocolLog;
+ }
+
+ abstract public function willReadBytes($bytes);
+ abstract public function willWriteBytes($bytes);
+
+}
diff --git a/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php
--- a/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php
+++ b/src/applications/diffusion/ssh/DiffusionGitSSHWorkflow.php
@@ -7,6 +7,8 @@
private $engineLogProperties = array();
private $protocolLog;
+ private $wireProtocol;
+
protected function writeError($message) {
// Git assumes we'll add our own newlines.
return parent::writeError($message."\n");
@@ -74,14 +76,24 @@
return null;
}
- protected function getProtocolLog() {
+ final protected function getProtocolLog() {
return $this->protocolLog;
}
- protected function setProtocolLog(PhabricatorProtocolLog $log) {
+ final protected function setProtocolLog(PhabricatorProtocolLog $log) {
$this->protocolLog = $log;
}
+ final protected function getWireProtocol() {
+ return $this->wireProtocol;
+ }
+
+ final protected function setWireProtocol(
+ DiffusionGitWireProtocol $protocol) {
+ $this->wireProtocol = $protocol;
+ return $this;
+ }
+
public function willWriteMessageCallback(
PhabricatorSSHPassthruCommand $command,
$message) {
@@ -91,6 +103,11 @@
$log->didWriteBytes($message);
}
+ $protocol = $this->getWireProtocol();
+ if ($protocol) {
+ $message = $protocol->willWriteBytes($message);
+ }
+
return $message;
}
@@ -103,6 +120,11 @@
$log->didReadBytes($message);
}
+ $protocol = $this->getWireProtocol();
+ if ($protocol) {
+ $message = $protocol->willReadBytes($message);
+ }
+
return $message;
}
diff --git a/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php
--- a/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php
+++ b/src/applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php
@@ -60,6 +60,16 @@
$log->didStartSession($command);
}
+ if (!$is_proxy) {
+ if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
+ $protocol = new DiffusionGitUploadPackWireProtocol();
+ if ($log) {
+ $protocol->setProtocolLog($log);
+ }
+ $this->setWireProtocol($protocol);
+ }
+ }
+
$err = $this->newPassthruCommand()
->setIOChannel($this->getIOChannel())
->setCommandChannelFromExecFuture($future)
diff --git a/src/infrastructure/log/PhabricatorProtocolLog.php b/src/infrastructure/log/PhabricatorProtocolLog.php
--- a/src/infrastructure/log/PhabricatorProtocolLog.php
+++ b/src/infrastructure/log/PhabricatorProtocolLog.php
@@ -41,6 +41,26 @@
$this->buffer[] = $bytes;
}
+ public function didReadFrame($frame) {
+ $this->writeFrame('<*', $frame);
+ }
+
+ public function didWriteFrame($frame) {
+ $this->writeFrame('>*', $frame);
+ }
+
+ private function writeFrame($header, $frame) {
+ $this->flush();
+
+ $frame = explode("\n", $frame);
+ foreach ($frame as $key => $line) {
+ $frame[$key] = $header.' '.$this->escapeBytes($line);
+ }
+ $frame = implode("\n", $frame)."\n\n";
+
+ $this->writeMessage($frame);
+ }
+
private function setMode($mode) {
if ($this->mode === $mode) {
return $this;

File Metadata

Mime Type
text/plain
Expires
Fri, May 10, 8:39 PM (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6284091
Default Alt Text
D20381.id.diff (14 KB)

Event Timeline