diff --git a/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php
index 4dab5f2972..915ae45970 100644
--- a/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php
+++ b/src/applications/diffusion/ssh/DiffusionSSHGitUploadPackWorkflow.php
@@ -23,4 +23,12 @@ final class DiffusionSSHGitUploadPackWorkflow
     return head($args->getArg('dir'));
   }
 
+  protected function executeRepositoryOperations(
+    PhabricatorRepository $repository) {
+
+    $future = new ExecFuture('git-upload-pack %s', $repository->getLocalPath());
+
+    return $this->passthruIO($future);
+  }
+
 }
diff --git a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php
index abee152496..5c2c182c9b 100644
--- a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php
+++ b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php
@@ -10,6 +10,9 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
 
   abstract protected function isReadOnly();
   abstract protected function getRequestPath();
+  abstract protected function executeRepositoryOperations(
+    PhabricatorRepository $repository);
+
   protected function writeError($message) {
     $this->getErrorChannel()->write($message);
     return $this;
@@ -20,15 +23,11 @@ abstract class DiffusionSSHWorkflow extends PhabricatorSSHWorkflow {
 
     try {
       $repository = $this->loadRepository();
-
-      throw new Exception("TODO: Implement serve over SSH.");
-
+      return $this->executeRepositoryOperations($repository);
     } catch (Exception $ex) {
       $this->writeError(get_class($ex).': '.$ex->getMessage());
       return 1;
     }
-
-    return 0;
   }
 
   private function loadRepository() {
diff --git a/src/infrastructure/ssh/PhabricatorSSHWorkflow.php b/src/infrastructure/ssh/PhabricatorSSHWorkflow.php
index 9f28d50c0a..60a9f03fcd 100644
--- a/src/infrastructure/ssh/PhabricatorSSHWorkflow.php
+++ b/src/infrastructure/ssh/PhabricatorSSHWorkflow.php
@@ -37,6 +37,50 @@ abstract class PhabricatorSSHWorkflow extends PhutilArgumentWorkflow {
     return $this->iochannel;
   }
 
+  public function passthruIO(ExecFuture $future) {
+    $exec_channel = new PhutilExecChannel($future);
+    $exec_channel->setStderrHandler(array($this, 'writeErrorIOCallback'));
+
+    $io_channel = $this->getIOChannel();
+    $error_channel = $this->getErrorChannel();
+
+    $channels = array($exec_channel, $io_channel, $error_channel);
+
+    while (true) {
+      PhutilChannel::waitForAny($channels);
+
+      $io_channel->update();
+      $exec_channel->update();
+      $error_channel->update();
+
+      $done = !$exec_channel->isOpen();
+
+      $data = $io_channel->read();
+      if (strlen($data)) {
+        $exec_channel->write($data);
+      }
+
+      $data = $exec_channel->read();
+      if (strlen($data)) {
+        $io_channel->write($data);
+      }
+
+      // If we have nothing left on stdin, close stdin on the subprocess.
+      if (!$io_channel->isOpenForReading()) {
+        // TODO: This should probably be part of PhutilExecChannel?
+        $future->write('');
+      }
+
+      if ($done) {
+        break;
+      }
+    }
+
+    list($err) = $future->resolve();
+
+    return $err;
+  }
+
   public function readAllInput() {
     $channel = $this->getIOChannel();
     while ($channel->update()) {
@@ -53,4 +97,13 @@ abstract class PhabricatorSSHWorkflow extends PhutilArgumentWorkflow {
     return $this;
   }
 
+  public function writeErrorIO($data) {
+    $this->getErrorChannel()->write($data);
+    return $this;
+  }
+
+  public function writeErrorIOCallback(PhutilChannel $channel, $data) {
+    $this->writeErrorIO($data);
+  }
+
 }