Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15319650
D8775.id20824.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
D8775.id20824.diff
View Options
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,207 @@
+<?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) {
+ if (($ex instanceof HTTPFutureResponseStatus) &&
+ ($ex->getStatusCode() == 404)) {
+ // This indicates that the object has been deleted (or never existed,
+ // or isn't visible to the current user) but it's a successful sync of
+ // an object which isn't visible.
+ } else {
+ // This is something else, so consider it a synchronization failure.
+ 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($githubAccount) {
+ // 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,85 @@
+<?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 Aphront500Response();
+
+ $payload = json_decode($_GET['payload']);
+ $type = $_GET['type'];
+ } 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');
+ }
+ } catch (Exception $ex) {
+ // This should be a 500.
+ return id(new AphrontPlainTextResponse())
+ ->setContent($ex);
+ }
+ }
+
+ private function handlePullRequest($payload) {
+ if (!$this->isGitHubRequest()) {
+ $pr_id = $_GET['id'];
+ } else {
+ $pr_id =
+ $payload->pull_request->base->repo->name.':'.
+ $payload->pull_request->base->repo->owner->login.':'.
+ $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
Details
Attached
Mime Type
text/plain
Expires
Fri, Mar 7, 9:22 PM (2 d, 9 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7350520
Default Alt Text
D8775.id20824.diff (13 KB)
Attached To
Mode
D8775: [DRAFT] Integrate GitHub pull requests as Differential reviews
Attached
Detach File
Event Timeline
Log In to Comment