Page MenuHomePhabricator

D7469.id16836.diff
No OneTemporary

D7469.id16836.diff

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

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)

Event Timeline