Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15477534
D7469.id16836.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
D7469.id16836.diff
View Options
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -91,6 +91,7 @@
'AphrontView' => 'view/AphrontView.php',
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
'AuditActionMenuEventListener' => 'applications/audit/events/AuditActionMenuEventListener.php',
+ 'AvivUtilController' => 'pocland/AvivUtilController.php',
'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php',
'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php',
'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php',
@@ -637,6 +638,7 @@
'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php',
'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
+ 'GithubApiCallFuture' => 'pocland/GithubApiCallFuture.php',
'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php',
@@ -728,6 +730,8 @@
'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php',
'JavelinViewExample' => 'applications/uiexample/examples/JavelinViewExample.php',
'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php',
+ 'LandLocally' => 'pocland/LandLocally.php',
+ 'LandRevisionController' => 'pocland/LandRevisionController.php',
'LegalpadConstants' => 'applications/legalpad/constants/LegalpadConstants.php',
'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php',
'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php',
@@ -2108,6 +2112,8 @@
'PonderVoteSaveController' => 'applications/ponder/controller/PonderVoteSaveController.php',
'ProjectCapabilityCreateProjects' => 'applications/project/capability/ProjectCapabilityCreateProjects.php',
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
+ 'PushCommitToGithub' => 'pocland/PushCommitToGithub.php',
+ 'PushToGitHubApp' => 'pocland/PushToGitHubApp.php',
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
@@ -2296,6 +2302,7 @@
),
'AphrontWebpageResponse' => 'AphrontHTMLResponse',
'AuditActionMenuEventListener' => 'PhabricatorEventListener',
+ 'AvivUtilController' => 'PhabricatorController',
'CelerityPhabricatorResourceController' => 'CelerityResourceController',
'CelerityResourceController' => 'PhabricatorController',
'CelerityResourceGraph' => 'AbstractDirectedGraph',
@@ -2832,6 +2839,7 @@
'FileCreateMailReceiver' => 'PhabricatorMailReceiver',
'FileMailReceiver' => 'PhabricatorObjectMailReceiver',
'FileReplyHandler' => 'PhabricatorMailReplyHandler',
+ 'GithubApiCallFuture' => 'FutureProxy',
'HarbormasterBuild' =>
array(
0 => 'HarbormasterDAO',
@@ -2952,6 +2960,8 @@
'JavelinUIExample' => 'PhabricatorUIExample',
'JavelinViewExample' => 'PhabricatorUIExample',
'JavelinViewExampleServerView' => 'AphrontView',
+ 'LandLocally' => 'AvivUtilController',
+ 'LandRevisionController' => 'AvivUtilController',
'LegalpadController' => 'PhabricatorController',
'LegalpadDAO' => 'PhabricatorLiskDAO',
'LegalpadDocument' =>
@@ -4533,6 +4543,8 @@
'PonderVoteSaveController' => 'PonderController',
'ProjectCapabilityCreateProjects' => 'PhabricatorPolicyCapability',
'ProjectRemarkupRule' => 'PhabricatorRemarkupRuleObject',
+ 'PushCommitToGithub' => 'AvivUtilController',
+ 'PushToGitHubApp' => 'PhabricatorApplication',
'QueryFormattingTestCase' => 'PhabricatorTestCase',
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
'ReleephBranch' =>
Index: src/pocland/AvivUtilController.php
===================================================================
--- /dev/null
+++ src/pocland/AvivUtilController.php
@@ -0,0 +1,66 @@
+<?php
+
+abstract class AvivUtilController extends PhabricatorController{
+
+ function build($result) {
+ return self::buildFor($this, $result);
+ }
+ function buildHumanReadableResponse(
+ $result) {
+ return self::buildFor($this, $result);
+ }
+
+ public static function buildFor($controller, array $data) {
+ $result = $data;
+
+ $param_rows = array();
+
+ $param_table = new AphrontTableView($param_rows);
+ $param_table->setDeviceReadyTable(true);
+ $param_table->setColumnClasses(
+ array(
+ 'header',
+ 'wide',
+ ));
+
+ $result_rows = array();
+ foreach ($result as $key => $value) {
+ $result_rows[] = array(
+ $key,
+ self::renderAPIValue($value),
+ );
+ }
+
+ $result_table = new AphrontTableView($result_rows);
+ $result_table->setDeviceReadyTable(true);
+ $result_table->setColumnClasses(
+ array(
+ 'header',
+ 'wide',
+ ));
+
+ $result_head = id(new PHUIHeaderView())
+ ->setHeader(pht('demagicking'));
+
+ return $controller->buildApplicationPage(
+ array(
+ $result_head,
+ $result_table,
+ ),
+ array(
+ 'title' => 'magic',
+ 'device' => true,
+ ));
+ }
+ static function renderAPIValue($value) {
+ $json = new PhutilJSON();
+ if (is_array($value)) {
+ $value = $json->encodeFormatted($value);
+ }
+
+ $value = hsprintf('<pre style="white-space: pre-wrap;">%s</pre>', $value);
+
+ return $value;
+ }
+
+}
Index: src/pocland/GithubApiCallFuture.php
===================================================================
--- /dev/null
+++ src/pocland/GithubApiCallFuture.php
@@ -0,0 +1,43 @@
+<?php
+
+class GithubApiCallFuture extends FutureProxy {
+ const BASE_URI = 'https://api.github.com/' ; // todo get this from provider.
+ const GITHUB_PROVIDER_KEY = 'github:github.com';
+
+ public $uri;
+ private $future;
+
+ public static function getAccessToken(PhabricatorUser $user) {
+ $account = id(new PhabricatorExternalAccountQuery())
+ ->setViewer($user)
+ ->withUserPHIDs(array($user->getPHID()))
+ ->withAccountDomains(array("github.com")) // todo get this from provider.
+ ->executeOne();
+
+ $github_provider = PhabricatorAuthProvider::getEnabledProviderByKey(
+ self::GITHUB_PROVIDER_KEY); // todo get this from somewhere.
+ return $github_provider->getOAuthAccessToken($account);
+ }
+
+ public function __construct($api, $access_token, array $data = null) {
+ $this->uri = new PhutilURI(self::BASE_URI . $api);
+ $this->uri->setQueryParam('access_token', $access_token);
+ $this->future = new HTTPSFuture($this->uri);
+ $this->setProxiedFuture($this->future);
+
+ if ($data !== null) {
+ $this->setData(json_encode($data));
+ }
+ }
+
+ protected function didReceiveResult($result){
+ return $result;
+ }
+
+ public function setMethod($method) {
+ $this->future->setMethod($method);
+ }
+ public function setData($encoded_data) {
+ $this->future->setData($encoded_data);
+ }
+}
Index: src/pocland/LandLocally.php
===================================================================
--- /dev/null
+++ src/pocland/LandLocally.php
@@ -0,0 +1,133 @@
+<?php
+
+class LandLocally extends AvivUtilController {
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+ $dd = array();
+ $good = true;
+
+ $revision_id = $request->getInt('revisionID');
+ if (!strlen($revision_id)) {
+ $dd['error'] = 'need param revisionID';
+ return $this->build($dd);
+ }
+
+ return $this->build(
+ $this->landRevisionLocally($revision_id, $viewer));
+ }
+
+ public function landRevisionLocally($revision_id, $viewer) {
+ $revision = id(new DifferentialRevisionQuery())
+ ->withIDs(array($revision_id))
+ ->setViewer($viewer)
+ ->executeOne();
+ if (!$revision) {
+ return array('error'=> "revision $revision_id not found");
+ }
+ $dd = array();
+
+ $diff = $revision->loadActiveDiff();
+ $diff_id = $diff->getID();
+
+ $call = new ConduitCall(
+ 'differential.getrawdiff',
+ array(
+ 'diffID' => $diff_id,
+ ));
+
+ $call->setUser($viewer);
+ $raw_diff = $call->execute();
+
+ $dd['raw_diff'] = strlen($raw_diff) > 500? strlen($raw_diff).' chars' : $raw_diff;
+ $tmp_file = new TempFile();
+ Filesystem::writeFile($tmp_file, $raw_diff);
+
+ $repo = id(new PhabricatorRepository())->
+ loadOneWhere('callsign = %s', 'TST');
+ $workspace = $this->getCleanWorkspace($repo);
+ $dd['workdir'] = $workspace->getPath();
+
+ try {
+ $workspace->execxLocal('apply --index %s', $tmp_file);
+ } catch (CommandException $ex) {
+ $dd['apply exception'] = $ex->getMessage();
+ $good = false;
+ }
+ $dd['status'] = $workspace->execxLocal('status');
+ $dd['status'] = $dd['status'][0];
+
+ $workspace->reloadWorkingCopy();
+
+ $call = new ConduitCall(
+ 'differential.getcommitmessage',
+ array(
+ 'revision_id' => $revision->getID(),
+ ));
+
+ $call->setUser($viewer);
+ $message = $call->execute();
+ // $dd['message'] = $message;
+
+ $author = id(new PhabricatorUser())->loadOneWhere(
+ 'phid = %s',
+ $revision->getAuthorPHID());
+
+
+ $author_string = $author->getRealName().' <'.$author->loadPrimaryEmailAddress().'>';
+ try {
+ $workspace->execxLocal(
+ '-c user.name=%s -c user.email=%s commit -m %s --author=%s',
+ // -c will set the 'commiter'
+ $viewer->getRealName(),
+ $viewer->loadPrimaryEmailAddress(),
+ $message,
+ $author_string);
+ } catch (CommandException $ex) {
+ $dd['commit exception'] = $ex->getMessage();
+ $good = false;
+ }
+
+ $dd['log'] = $workspace->execxLocal('log -1 --format=fuller');
+ $dd['log'] = $dd['log'][0];
+
+ $dd['landed locally'] = $good;
+
+ return $dd;
+ }
+
+ function getCleanWorkspace(PhabricatorRepository $repo) {
+ $origin_path = $repo->getLocalPath();
+
+ $path = rtrim($origin_path, '/');
+ $path = $path . '__workspace';
+
+ if (! Filesystem::pathExists($path)) {
+ $future = new ExecFuture(
+ 'git clone file://%s %s',
+ $origin_path,
+ $path);
+ $future->resolve();
+ }
+
+ $workspace = new ArcanistGitAPI($path);
+ $workspace->execxLocal('clean -fd');
+ $workspace->execxLocal('checkout master');
+ $workspace->execxLocal('fetch');
+ $workspace->execxLocal('reset --hard origin/master');
+ $workspace->reloadWorkingCopy();
+
+ return $workspace;
+ }
+
+ public function loadFileByPHID($phid) {
+ $file = id(new PhabricatorFile())->loadOneWhere(
+ 'phid = %s',
+ $phid);
+ if (!$file) {
+ return null;
+ }
+ return $file->loadFileData();
+ }
+
+}
\ No newline at end of file
Index: src/pocland/LandRevisionController.php
===================================================================
--- /dev/null
+++ src/pocland/LandRevisionController.php
@@ -0,0 +1,62 @@
+<?php
+
+class LandRevisionController extends AvivUtilController {
+
+ const GITHUB_PROVIDER_KEY = 'github:github.com';
+ const TEST_REPO_GIT_ID = 13850529;
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $revision_id = $request->getInt('revisionID');
+ $github_repo = $request->getStr('githubRepo');
+
+ if ($revision_id != null && strlen($github_repo)) {
+
+ $log = id(new LandLocally($request))
+ ->landRevisionLocally($revision_id, $viewer);
+
+ if (idx($log, 'landed locally') !== true) {
+ return $this->build($log);
+ }
+
+ $workdir = $log['workdir'];
+ $log2 = id(new PushCommitToGithub($request))
+ ->pushLastCommit($workdir, $github_repo, $viewer);
+
+ return $this->build($log + $log2);
+ }
+
+ $panel = id(new AphrontPanelView())
+ ->setHeader("Landing to GH demo");
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer);
+
+ $form->appendChild(id(new AphrontFormTextControl())
+ ->setName('revisionID')
+ ->setLabel('revision ID')
+ );
+
+ $form->appendChild(id(new AphrontFormTextControl())
+ ->setName('githubRepo')
+ ->setLabel('Github Repo')
+ ->setValue('avivey/test-repo')
+ ->setCaption('should be available from the repo')
+ );
+
+ $form->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Submit'));
+ $panel->appendChild($form);
+
+ return $this->buildApplicationPage(
+ $panel,
+ array(
+ 'title' => 'UI Example',
+ 'device' => true,
+ ));
+ }
+}
+
Index: src/pocland/PushCommitToGithub.php
===================================================================
--- /dev/null
+++ src/pocland/PushCommitToGithub.php
@@ -0,0 +1,215 @@
+<?php
+
+class PushCommitToGithub extends AvivUtilController {
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $pwd = '/tmp/phwork/tst__workspace/';
+ return $this->build(
+ $this->pushLastCommit($pwd, 'avivey/test-repo', $viewer));
+ }
+
+ public function pushLastCommit($workdir, $github_repo, $viewer) {
+ $pwd = $workdir;
+
+ $future = new ExecFuture(
+ '/bin/bash -c %s',
+ 'comm -23 <(git ls-tree -r HEAD |sort) <(git ls-tree -r HEAD~ |sort)');
+ $future->setCwd($pwd);
+
+ list($err, $stdout, $stderr) = $future->resolve();
+ $dd = array('exit'=>$err, 'out'=>$stdout, 'err'=>$stderr);
+ $parsed = $this->parseGitTree($stdout);
+ $dd['parsed'] = $parsed;
+
+ $new_blobs = array();
+ foreach ($parsed as $data) {
+ if ($data['type'] === 'blob') {
+ $new_blobs[$data['sha']] = $data;
+ }
+ }
+
+ $access_token = GithubApiCallFuture::getAccessToken($viewer);
+ $dd['token'] = $access_token;
+ $futures = array();
+ foreach ($new_blobs as $blob) {
+ $content = file_get_contents($pwd.$blob['path']);
+ $content = base64_encode($content);
+ $future =
+ self::makePushBlobFuture('avivey/test-repo', $content, $access_token);
+ $futures[$blob['sha']] = $future;
+ }
+
+ $repo = 'avivey/test-repo';
+
+ $parsed = array();
+ foreach ($futures as $hash => $future) {
+ $res = self::parseGithubApiFuture($future);
+ $res['WIN'] = idx($res['body'], 'sha') == $hash;
+ $parsed[$hash] = $res;
+ }
+ $dd['creat blobs call'] = $parsed;
+
+ $future = new ExecFuture(
+ '/bin/bash -c %s',
+ 'git cat-file commit HEAD');
+ $future->setCwd($pwd);
+ $commit_info = $future->resolvex();
+ $commit_info = $commit_info[0];
+
+ $commit_info = $this->parseCommit($commit_info);
+ $dd['wanted tree hash'] = $commit_info;
+
+ $future = new ExecFuture(
+ '/bin/bash -c %s',
+ 'git ls-tree -r HEAD');
+ $future->setCwd($pwd);
+ $head_tree = $future->resolvex();
+ $head_tree = $this->parseGitTree($head_tree[0]);
+ $dd['tree'] = json_encode($head_tree);
+
+ $future = $this->makeCreateTreeFuture('avivey/test-repo', $head_tree, $access_token);
+ $dd['create tree call'] = self::parseGithubApiFuture($future);
+
+ // todo parse author info + time.
+ $future = $this->makeCommitFuture(
+ 'avivey/test-repo',
+ $commit_info['message'],
+ $commit_info['tree'],
+ $commit_info['parent'],
+ array(), // TODO add author data.
+ $access_token
+ );
+ $commit = self::parseGithubApiFuture($future);
+ $dd['create make commit'] = $commit;
+ $commit = idx($commit, 'body');
+ $commit = idx($commit, 'sha');
+
+ if ($commit) {
+ $future = $this->makeUpdateMasterFuture($repo, $commit, $access_token);
+ $dd['update'] = self::parseGithubApiFuture($future);
+ }
+ return $dd;
+ }
+
+ function makeCommitFuture($repo, $message, $tree, $parent, array $more, $access_token) {
+ // todo add committer data
+ // todo get information from phabricator commit object.
+
+ $author = idx($more, 'author');
+
+ $data = array(
+ 'message' => $message,
+ 'tree' => $tree,
+ 'parents' => array($parent),
+ );
+ if ($author) {
+ $data['author'] = $author;
+ }
+ $future = new GithubApiCallFuture(
+ "repos/$repo/git/commits",
+ $access_token,
+ $data);
+ $future->setMethod('POST');
+
+ return $future->start();
+ }
+
+ function makeUpdateMasterFuture($repo, $commit, $access_token) {
+ $data = array('sha' => $commit);
+
+ $future = new GithubApiCallFuture(
+ "repos/$repo/git/refs/heads/master",
+ $access_token,
+ $data);
+ $future->setMethod('PATCH');
+ return $future->start();
+ }
+
+ function makeCreateTreeFuture($repo, $tree, $access_token) {
+ $data = array('tree' => $tree);
+
+ $future = new GithubApiCallFuture(
+ "repos/$repo/git/trees",
+ $access_token,
+ $data);
+ $future->setMethod('POST');
+ return $future->start();
+ }
+
+ function makePushBlobFuture($repo, $blob_in_base64, $access_token) {
+ $data = array(
+ 'encoding' => 'base64',
+ 'content' => $blob_in_base64,
+ );
+ $future = new GithubApiCallFuture(
+ "repos/$repo/git/blobs",
+ $access_token,
+ $data);
+ $future->setMethod('POST');
+ $future->start();
+ return $future;
+ }
+
+ function parseGithubApiFuture($future) {
+ $dd = array();
+ // $dd['uri'] = ''.$future->uri;
+ list($status, $body, $headers) = $future->resolve();
+
+ $dd['api status'] = $status->getStatusCode();
+ // $dd['headers'] = $headers;
+ $dd['body'] = json_decode($body, true);
+ return $dd;
+ }
+
+ private function parseGitTree($stdout) {
+ $result = array();
+
+ $stdout = trim($stdout);
+ if (!strlen($stdout)) {
+ return $result;
+ }
+
+ $lines = explode("\n", $stdout);
+ foreach ($lines as $line) {
+ $matches = array();
+ $ok = preg_match(
+ '/^(\d{6}) (blob|tree|commit) ([a-z0-9]{40})[\t](.*)$/',
+ $line,
+ $matches);
+ if (!$ok) {
+ throw new Exception("Failed to parse git ls-tree output!");
+ }
+ // this now matches github's api.
+ $result[] = array(
+ 'mode' => $matches[1],
+ 'type' => $matches[2],
+ 'sha' => $matches[3],
+ 'path' => $matches[4],
+ );
+ }
+ return $result;
+ }
+
+ function parseCommit($commit) {
+ $commit = trim($commit);
+ $text = explode("\n", $commit);
+ $data = array();
+ $message = array();
+ $in_message = false;
+ while(!empty($text)) {
+ $line = array_shift($text);
+ if ($in_message) {
+ array_push($message, $line);
+ } else if (empty($line)) {
+ $in_message = true;
+ } else {
+ $line = explode(' ', $line, 2);
+ $data[$line[0]] = $line[1];
+ }
+ }
+ $data['message'] = implode("\n", $message);
+ return $data;
+ }
+}
\ No newline at end of file
Index: src/pocland/PushToGitHubApp.php
===================================================================
--- /dev/null
+++ src/pocland/PushToGitHubApp.php
@@ -0,0 +1,27 @@
+<?php
+
+class PushToGitHubApp extends PhabricatorApplication {
+ public function getBaseURI() {
+ return '/magic/';
+ }
+
+ public function getRoutes() {
+ return array(
+ '/magic/' => 'LandRevisionController',
+ '/local-magic/' => 'LandLocally',
+ '/big-magic/' => 'PushToGitFindDetailsController',
+ );
+ }
+
+ public function getShortDescription() {
+ return "magick";
+ }
+
+ public function getIconName() {
+ return 'github';
+ }
+
+ public function getApplicationGroup() {
+ return self::GROUP_CORE;
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Apr 8, 4:01 PM (1 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7588085
Default Alt Text
D7469.id16836.diff (19 KB)
Attached To
Mode
D7469: POC: Land a revision to Github from Web UI
Attached
Detach File
Event Timeline
Log In to Comment