Page MenuHomePhabricator

D8775.diff
No OneTemporary

D8775.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
@@ -614,6 +614,7 @@
'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php',
'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php',
'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php',
+ 'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php',
'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php',
'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php',
'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php',
@@ -622,6 +623,7 @@
'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php',
'DoorkeeperFeedWorkerAsana' => 'applications/doorkeeper/worker/DoorkeeperFeedWorkerAsana.php',
'DoorkeeperFeedWorkerJIRA' => 'applications/doorkeeper/worker/DoorkeeperFeedWorkerJIRA.php',
+ 'DoorkeeperGitHubHookController' => 'applications/doorkeeper/controller/DoorkeeperGitHubHookController.php',
'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php',
'DoorkeeperMissingLinkException' => 'applications/doorkeeper/exception/DoorkeeperMissingLinkException.php',
'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php',
@@ -3195,6 +3197,7 @@
'DivinerWorkflow' => 'PhabricatorManagementWorkflow',
'DoorkeeperBridge' => 'Phobject',
'DoorkeeperBridgeAsana' => 'DoorkeeperBridge',
+ 'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge',
'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge',
'DoorkeeperDAO' => 'PhabricatorLiskDAO',
'DoorkeeperExternalObject' =>
@@ -3206,6 +3209,7 @@
'DoorkeeperFeedWorker' => 'FeedPushWorker',
'DoorkeeperFeedWorkerAsana' => 'DoorkeeperFeedWorker',
'DoorkeeperFeedWorkerJIRA' => 'DoorkeeperFeedWorker',
+ 'DoorkeeperGitHubHookController' => 'PhabricatorController',
'DoorkeeperImportEngine' => 'Phobject',
'DoorkeeperMissingLinkException' => 'Exception',
'DoorkeeperObjectRef' => 'Phobject',
diff --git a/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php b/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php
--- a/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php
+++ b/src/applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php
@@ -25,6 +25,7 @@
return array(
'/doorkeeper/' => array(
'tags/' => 'DoorkeeperTagsController',
+ 'github-hook/' => 'DoorkeeperGitHubHookController',
),
);
}
diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php
new file mode 100644
--- /dev/null
+++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php
@@ -0,0 +1,200 @@
+<?php
+
+final class DoorkeeperBridgeGitHub extends DoorkeeperBridge {
+
+ const APPTYPE_GITHUB = 'github';
+ const APPDOMAIN_GITHUB ='github.com';
+ const OBJTYPE_PULL_REQUEST = 'github:pull-request';
+
+ public function canPullRef(DoorkeeperObjectRef $ref) {
+ if ($ref->getApplicationType() != self::APPTYPE_GITHUB) {
+ return false;
+ }
+
+ $types = array(
+ self::OBJTYPE_PULL_REQUEST => true,
+ );
+
+ return isset($types[$ref->getObjectType()]);
+ }
+
+ public function pullRefs(array $refs) {
+
+ $id_map = mpull($refs, 'getObjectID', 'getObjectKey');
+
+ // Object IDs for pull requests are formatted as:
+ //
+ // user:repo:pullid
+ //
+ // since we need both the user and repo name to retrieve
+ // data about a pull request.
+ $data_map = array();
+ foreach ($id_map as $key => $id) {
+ $split = explode(':', $id);
+ $user = str_replace('/', '', $split[0]);
+ $repo = str_replace('/', '', $split[1]);
+ $pull_id = (int)$split[2];
+ $data_map[$key] = array(
+ 'id' => $id,
+ 'user' => $user,
+ 'repo' => $repo,
+ 'pullID' => $pull_id,
+ 'getURI' =>
+ 'https://api.github.com/repos/'.$user.'/'.$repo.'/pulls/'.$pull_id);
+ }
+
+ // Query the GitHub public API, retrieving all of the information.
+ $futures = array();
+ $user_agent =
+ 'Phabricator Instance at '.
+ PhabricatorEnv::getProductionURI('/');
+ foreach ($data_map as $key => $data) {
+ $futures[$key] = id(new HTTPSFuture($data['getURI']))
+ ->setMethod('GET')
+ ->setTimeout(30)
+ ->addHeader('User-Agent', $user_agent);
+ }
+
+ $results = array();
+ $failed = array();
+ foreach (Futures($futures) as $key => $future) {
+ try {
+ $response = $future->resolvex();
+ $results[$key] = json_decode($response[0], true);
+ } catch (Exception $ex) {
+ phlog($ex);
+ $failed[$key] = $ex;
+ }
+ }
+
+ // Set all the associated data.
+ foreach ($refs as $ref) {
+ $did_fail = idx($failed, $ref->getObjectKey());
+ if ($did_fail) {
+ // Set at least the key to identify, normally the
+ // name is the name of the PR.
+ $ref->setAttribute('name', 'GitHub PR '.$ref->getObjectID());
+ $ref->setSyncFailed(true);
+ continue;
+ }
+
+ $result = idx($results, $ref->getObjectKey());
+ if (!$result) {
+ continue;
+ }
+
+ $ref->setIsVisible(true);
+ $ref->setAttribute('name', $result['title']);
+
+ // TODO: Does the data belong on the ref attributes or the
+ // object properties??
+
+
+ $obj = $ref->getExternalObject();
+ $obj->setObjectURI($result['url']);
+ $obj->setProperty('name', $result['title']);
+ $obj->setProperty('diffURL', $result['diff_url']);
+ $obj->setProperty('state', $result['state']);
+ $obj->setProperty('userLogin', $result['user']['login']);
+ $obj->setProperty('body', $result['body']);
+ $obj->setProperty('headGitURL', $result['head']['repo']['git_url']);
+ $obj->setProperty('headGitSHA', $result['head']['sha']);
+ $obj->setProperty('headLabel', $result['head']['label']);
+ $obj->setProperty('headOwnerLogin', $result['head']['owner']['login']);
+ $obj->setProperty('baseGitURL', $result['base']['repo']['git_url']);
+ $obj->setProperty('baseGitSHA', $result['base']['sha']);
+ $obj->setProperty('baseLabel', $result['base']['label']);
+ $obj->setProperty('baseOwnerLogin', $result['base']['owner']['login']);
+ $obj->setProperty('merged', $result['merged']);
+
+ $existing_revision_id = $obj->getProperty('revisionID');
+ $revision_id = $this->syncRevisionForExternalObject(
+ $obj,
+ $existing_revision_id);
+ $obj->setProperty('revisionID', $revision_id);
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $obj->save();
+ unset($unguarded);
+ }
+ }
+
+ private function syncRevisionForExternalObject(
+ DoorkeeperExternalObject $pr,
+ $existing_revision_id) {
+
+ $author_user = $this->lookupPhabricatorUserByConnectedAccount(
+ $pr->getProperty('userLogin'));
+
+ if ($existing_revision_id === null) {
+ // There is no existing revision, so we need to create one.
+ $revision = DifferentialRevision::initializeNewRevision($author_user);
+ $revision->attachReviewerStatus(array());
+ } else {
+ $revision = id(new DifferentialRevisionQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withIDs(array($existing_revision_id))
+ ->needReviewerStatus(true)
+ ->needActiveDiffs(true)
+ ->executeOne();
+ }
+
+ $revision->setAuthorPHID($author_user->getPHID());
+ $revision->setTitle($pr->getProperty('name'));
+ $revision->setSummary($pr->getProperty('body'));
+
+ // Download diff.
+ $user_agent =
+ 'Phabricator Instance at '.
+ PhabricatorEnv::getProductionURI('/');
+ $response = id(new HTTPSFuture($pr->getProperty('diffURL')))
+ ->setMethod('GET')
+ ->setTimeout(30)
+ ->addHeader('User-Agent', $user_agent)
+ ->resolvex();
+ $raw_diff = $response[0];
+
+ // Copied from Conduit code? Should we just make
+ // the Conduit call here?
+ $parser = new ArcanistDiffParser();
+ $changes = $parser->parseDiff($raw_diff);
+ $diff = DifferentialDiff::newFromRawChanges($changes);
+
+ $diff->setLintStatus(DifferentialLintStatus::LINT_SKIP);
+ $diff->setUnitStatus(DifferentialUnitStatus::UNIT_SKIP);
+
+ $diff->setDescription('Received from GitHub service hook');
+
+ $diff->setAuthorPHID($author_user->getPHID());
+ $diff->setCreationMethod('github');
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $diff->save();
+ unset($unguarded);
+
+ // Attach diff.
+ $xactions = array();
+ $xactions[] = id(new DifferentialTransaction())
+ ->setTransactionType(DifferentialTransaction::TYPE_UPDATE)
+ ->setNewValue($diff->getPHID());
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $editor = id(new DifferentialTransactionEditor())
+ ->setActor($author_user)
+ ->setContentSource(
+ PhabricatorContentSource::newForSource('github', array()))
+ ->setContinueOnNoEffect(true)
+ ->applyTransactions($revision, $xactions);
+ unset($unguarded);
+
+ return $revision->getID();
+ }
+
+ private function lookupPhabricatorUserByConnectedAccount($github_account) {
+ // TODO
+ return id(new PhabricatorPeopleQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withUsernames(array('github'))
+ ->executeOne();
+ }
+
+}
diff --git a/src/applications/doorkeeper/controller/DoorkeeperGitHubHookController.php b/src/applications/doorkeeper/controller/DoorkeeperGitHubHookController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/doorkeeper/controller/DoorkeeperGitHubHookController.php
@@ -0,0 +1,77 @@
+<?php
+
+final class DoorkeeperGitHubHookController extends PhabricatorController {
+
+ public function shouldRequireLogin() {
+ return !$this->isGitHubRequest();
+ }
+
+ public function shouldRequireEnabledUser() {
+ return !$this->isGitHubRequest();
+ }
+
+ public function shouldAllowPublic() {
+ return $this->isGitHubRequest();
+ }
+
+ private function isGitHubRequest() {
+ return
+ $_SERVER['REQUEST_METHOD'] == 'POST' &&
+ $_SERVER['HTTP_X_GITHUB_EVENT'] != '';
+ }
+
+ public function processRequest() {
+ if (!$this->isGitHubRequest()) {
+ // TODO: Maybe display something to the user telling
+ // them that this is the GitHub endpoint and how to
+ // configure it?
+ return new Aphront404Response();
+ } else {
+ $payload = json_decode($_POST['payload']);
+ $type = $_SERVER['HTTP_X_GITHUB_EVENT'];
+ }
+
+ try {
+ switch ($type) {
+ case "pull_request":
+ return $this->handlePullRequest($payload);
+ default:
+ return id(new AphrontPlainTextResponse())
+ ->setContent('okay (got '.$type.')');
+ }
+ } catch (Exception $ex) {
+ // This should be a 500.
+ return id(new AphrontPlainTextResponse())
+ ->setContent($ex);
+ }
+ }
+
+ private function handlePullRequest($payload) {
+ $pr_id =
+ $payload->pull_request->base->repo->owner->login.':'.
+ $payload->pull_request->base->repo->name.':'.
+ $payload->number;
+
+ // Push this through the Doorkeeper import engine.
+ $ref = id(new DoorkeeperObjectRef())
+ ->setApplicationType(DoorkeeperBridgeGitHub::APPTYPE_GITHUB)
+ ->setApplicationDomain(DoorkeeperBridgeGitHub::APPDOMAIN_GITHUB)
+ ->setObjectType(DoorkeeperBridgeGitHub::OBJTYPE_PULL_REQUEST)
+ ->setObjectID($pr_id);
+
+ try {
+ $refs = id(new DoorkeeperImportEngine())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->setRefs(array($ref))
+ ->setThrowOnMissingLink(true)
+ ->execute();
+ } catch (DoorkeeperMissingLinkException $ex) {
+ return id(new AphrontPlainTextResponse())
+ ->setContent('no link');
+ }
+
+ return id(new AphrontPlainTextResponse())
+ ->setContent($pr_id.' imported');
+ }
+
+}
diff --git a/src/applications/metamta/contentsource/PhabricatorContentSource.php b/src/applications/metamta/contentsource/PhabricatorContentSource.php
--- a/src/applications/metamta/contentsource/PhabricatorContentSource.php
+++ b/src/applications/metamta/contentsource/PhabricatorContentSource.php
@@ -14,6 +14,7 @@
const SOURCE_LEGACY = 'legacy';
const SOURCE_DAEMON = 'daemon';
const SOURCE_LIPSUM = 'lipsum';
+ const SOURCE_GITHUB = 'github';
private $source;
private $params = array();
@@ -77,6 +78,7 @@
self::SOURCE_DAEMON => pht('Daemons'),
self::SOURCE_LIPSUM => pht('Lipsum'),
self::SOURCE_UNKNOWN => pht('Old World'),
+ self::SOURCE_GITHUB => pht('GitHub'),
);
}

File Metadata

Mime Type
text/plain
Expires
Thu, May 9, 2:11 AM (4 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6270205
Default Alt Text
D8775.diff (12 KB)

Event Timeline