[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class DifferentialLandingToGitHub 4 extends DifferentialLandingStrategy { 5 6 private $account; 7 private $provider; 8 9 public function processLandRequest( 10 AphrontRequest $request, 11 DifferentialRevision $revision, 12 PhabricatorRepository $repository) { 13 14 $viewer = $request->getUser(); 15 $this->init($viewer, $repository); 16 17 $workspace = $this->getGitWorkspace($repository); 18 19 try { 20 id(new DifferentialLandingToHostedGit()) 21 ->commitRevisionToWorkspace( 22 $revision, 23 $workspace, 24 $viewer); 25 } catch (Exception $e) { 26 throw new PhutilProxyException( 27 'Failed to commit patch', 28 $e); 29 } 30 31 try { 32 $this->pushWorkspaceRepository($repository, $workspace); 33 } catch (Exception $e) { 34 // If it's a permission problem, we know more than git. 35 $dialog = $this->verifyRemotePermissions($viewer, $revision, $repository); 36 if ($dialog) { 37 return $dialog; 38 } 39 40 // Else, throw what git said. 41 throw new PhutilProxyException( 42 'Failed to push changes upstream', 43 $e); 44 } 45 } 46 47 /** 48 * returns PhabricatorActionView or an array of PhabricatorActionView or null. 49 */ 50 public function createMenuItem( 51 PhabricatorUser $viewer, 52 DifferentialRevision $revision, 53 PhabricatorRepository $repository) { 54 55 $vcs = $repository->getVersionControlSystem(); 56 if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { 57 return; 58 } 59 60 if ($repository->isHosted()) { 61 return; 62 } 63 64 try { 65 // These throw when failing. 66 $this->init($viewer, $repository); 67 $this->findGitHubRepo($repository); 68 } catch (Exception $e) { 69 return; 70 } 71 72 return $this->createActionView($revision, pht('Land to GitHub')) 73 ->setIcon('fa-cloud-upload'); 74 } 75 76 public function pushWorkspaceRepository( 77 PhabricatorRepository $repository, 78 ArcanistRepositoryAPI $workspace) { 79 80 $token = $this->getAccessToken(); 81 82 $github_repo = $this->findGitHubRepo($repository); 83 84 $remote = urisprintf( 85 'https://%s:x-oauth-basic@%s/%s.git', 86 $token, 87 $this->provider->getProviderDomain(), 88 $github_repo); 89 90 $workspace->execxLocal( 91 'push %P HEAD:master', 92 new PhutilOpaqueEnvelope($remote)); 93 } 94 95 private function init($viewer, $repository) { 96 $repo_uri = $repository->getRemoteURIObject(); 97 $repo_domain = $repo_uri->getDomain(); 98 99 $this->account = id(new PhabricatorExternalAccountQuery()) 100 ->setViewer($viewer) 101 ->withUserPHIDs(array($viewer->getPHID())) 102 ->withAccountTypes(array('github')) 103 ->withAccountDomains(array($repo_domain)) 104 ->requireCapabilities( 105 array( 106 PhabricatorPolicyCapability::CAN_VIEW, 107 PhabricatorPolicyCapability::CAN_EDIT, 108 )) 109 ->executeOne(); 110 111 if (!$this->account) { 112 throw new Exception( 113 "No matching GitHub account found for {$repo_domain}."); 114 } 115 116 $this->provider = PhabricatorAuthProvider::getEnabledProviderByKey( 117 $this->account->getProviderKey()); 118 if (!$this->provider) { 119 throw new Exception("GitHub provider for {$repo_domain} is not enabled."); 120 } 121 } 122 123 private function findGitHubRepo(PhabricatorRepository $repository) { 124 $repo_uri = $repository->getRemoteURIObject(); 125 126 $repo_path = $repo_uri->getPath(); 127 128 if (substr($repo_path, -4) == '.git') { 129 $repo_path = substr($repo_path, 0, -4); 130 } 131 $repo_path = ltrim($repo_path, '/'); 132 133 return $repo_path; 134 } 135 136 private function getAccessToken() { 137 return $this->provider->getOAuthAccessToken($this->account); 138 } 139 140 private function verifyRemotePermissions($viewer, $revision, $repository) { 141 $github_user = $this->account->getUsername(); 142 $github_repo = $this->findGitHubRepo($repository); 143 144 $uri = urisprintf( 145 'https://api.github.com/repos/%s/collaborators/%s', 146 $github_repo, 147 $github_user); 148 149 $uri = new PhutilURI($uri); 150 $uri->setQueryParam('access_token', $this->getAccessToken()); 151 list($status, $body, $headers) = id(new HTTPSFuture($uri))->resolve(); 152 153 // Likely status codes: 154 // 204 No Content: Has permissions. Token might be too weak. 155 // 404 Not Found: Not a collaborator. 156 // 401 Unauthorized: Token is bad/revoked. 157 158 $no_permission = ($status->getStatusCode() == 404); 159 160 if ($no_permission) { 161 throw new Exception( 162 "You don't have permission to push to this repository. \n". 163 "Push permissions for this repository are managed on GitHub."); 164 } 165 166 $scopes = BaseHTTPFuture::getHeader($headers, 'X-OAuth-Scopes'); 167 if (strpos($scopes, 'public_repo') === false) { 168 $provider_key = $this->provider->getProviderKey(); 169 $refresh_token_uri = new PhutilURI("/auth/refresh/{$provider_key}/"); 170 $refresh_token_uri->setQueryParam('scope', 'public_repo'); 171 172 return id(new AphrontDialogView()) 173 ->setUser($viewer) 174 ->setTitle(pht('Stronger token needed')) 175 ->appendChild(pht( 176 'In order to complete this action, you need a '. 177 'stronger GitHub token.')) 178 ->setSubmitURI($refresh_token_uri) 179 ->addCancelButton('/D'.$revision->getId()) 180 ->setDisableWorkflowOnSubmit(true) 181 ->addSubmitButton(pht('Refresh Account Link')); 182 } 183 } 184 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |