PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/src/applications/conduit/method/diffusion/getcommits/ConduitAPI_diffusion_getcommits_Method.php

http://github.com/facebook/phabricator
PHP | 273 lines | 187 code | 42 blank | 44 comment | 12 complexity | ca5d3a3a9d8e6e5218d01aac10e27263 MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * Copyright 2011 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @group conduit
  19. */
  20. class ConduitAPI_diffusion_getcommits_Method extends ConduitAPIMethod {
  21. public function getMethodDescription() {
  22. return "Retrieve Diffusion commit information.";
  23. }
  24. public function defineParamTypes() {
  25. return array(
  26. 'commits' => 'required list<string>',
  27. );
  28. }
  29. public function defineReturnType() {
  30. return 'nonempty list<dict<string, wild>>';
  31. }
  32. public function defineErrorTypes() {
  33. return array(
  34. );
  35. }
  36. protected function execute(ConduitAPIRequest $request) {
  37. $results = array();
  38. $commits = $request->getValue('commits');
  39. $commits = array_fill_keys($commits, array());
  40. foreach ($commits as $name => $info) {
  41. $matches = null;
  42. if (!preg_match('/^r([A-Z]+)([0-9a-f]+)$/', $name, $matches)) {
  43. $results[$name] = array(
  44. 'error' => 'ERR-UNPARSEABLE',
  45. );
  46. unset($commits[$name]);
  47. continue;
  48. }
  49. $commits[$name] = array(
  50. 'callsign' => $matches[1],
  51. 'commitIdentifier' => $matches[2],
  52. );
  53. }
  54. if (!$commits) {
  55. return $results;
  56. }
  57. $callsigns = ipull($commits, 'callsign');
  58. $callsigns = array_unique($callsigns);
  59. $repos = id(new PhabricatorRepository())->loadAllWhere(
  60. 'callsign IN (%Ls)',
  61. $callsigns);
  62. $repos = mpull($repos, null, 'getCallsign');
  63. foreach ($commits as $name => $info) {
  64. $repo = idx($repos, $info['callsign']);
  65. if (!$repo) {
  66. $results[$name] = $info + array(
  67. 'error' => 'ERR-UNKNOWN-REPOSITORY',
  68. );
  69. unset($commits[$name]);
  70. continue;
  71. }
  72. $commits[$name] += array(
  73. 'repositoryPHID' => $repo->getPHID(),
  74. 'repositoryID' => $repo->getID(),
  75. );
  76. }
  77. if (!$commits) {
  78. return $results;
  79. }
  80. // Execute a complicated query to figure out the primary commit information
  81. // for each referenced commit.
  82. $cdata = $this->queryCommitInformation($commits, $repos);
  83. // We've built the queries so that each row also has the identifier we used
  84. // to select it, which might be a git prefix rather than a full identifier.
  85. $ref_map = ipull($cdata, 'commitIdentifier', 'commitRef');
  86. $cobjs = id(new PhabricatorRepositoryCommit())->loadAllFromArray($cdata);
  87. $cobjs = mgroup($cobjs, 'getRepositoryID', 'getCommitIdentifier');
  88. foreach ($commits as $name => $commit) {
  89. // Expand short git names into full identifiers. For SVN this map is just
  90. // the identity.
  91. $full_identifier = idx($ref_map, $commit['commitIdentifier']);
  92. $repo_id = $commit['repositoryID'];
  93. unset($commits[$name]['repositoryID']);
  94. if (empty($full_identifier) ||
  95. empty($cobjs[$commit['repositoryID']][$full_identifier])) {
  96. $results[$name] = $commit + array(
  97. 'error' => 'ERR-UNKNOWN-COMMIT',
  98. );
  99. unset($commits[$name]);
  100. continue;
  101. }
  102. $cobj_arr = $cobjs[$commit['repositoryID']][$full_identifier];
  103. $cobj = head($cobj_arr);
  104. $commits[$name] += array(
  105. 'epoch' => $cobj->getEpoch(),
  106. 'commitPHID' => $cobj->getPHID(),
  107. 'commitID' => $cobj->getID(),
  108. );
  109. // Upgrade git short references into full commit identifiers.
  110. $identifier = $cobj->getCommitIdentifier();
  111. $commits[$name]['commitIdentifier'] = $identifier;
  112. $callsign = $commits[$name]['callsign'];
  113. $uri = "/r{$callsign}{$identifier}";
  114. $commits[$name]['uri'] = PhabricatorEnv::getProductionURI($uri);
  115. }
  116. if (!$commits) {
  117. return $results;
  118. }
  119. $commits = $this->addRepositoryCommitDataInformation($commits);
  120. $commits = $this->addDifferentialInformation($commits);
  121. foreach ($commits as $name => $commit) {
  122. $results[$name] = $commit;
  123. }
  124. return $results;
  125. }
  126. /**
  127. * Retrieve primary commit information for all referenced commits.
  128. */
  129. private function queryCommitInformation(array $commits, array $repos) {
  130. $conn_r = id(new PhabricatorRepositoryCommit())->establishConnection('r');
  131. $repos = mpull($repos, null, 'getID');
  132. $groups = array();
  133. foreach ($commits as $name => $commit) {
  134. $groups[$commit['repositoryID']][] = $commit['commitIdentifier'];
  135. }
  136. // NOTE: MySQL goes crazy and does a massive table scan if we build a more
  137. // sensible version of this query. Make sure the query plan is OK if you
  138. // attempt to reduce the craziness here. METANOTE: The addition of prefix
  139. // selection for Git further complicates matters.
  140. $query = array();
  141. $commit_table = id(new PhabricatorRepositoryCommit())->getTableName();
  142. foreach ($groups as $repository_id => $identifiers) {
  143. $vcs = $repos[$repository_id]->getVersionControlSystem();
  144. $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
  145. if ($is_git) {
  146. foreach ($identifiers as $identifier) {
  147. if (strlen($identifier) < 7) {
  148. // Don't bother with silly stuff like 'rX2', which will select
  149. // 1/16th of all commits. Note that with length 7 we'll still get
  150. // collisions in repositories at the tens-of-thousands-of-commits
  151. // scale.
  152. continue;
  153. }
  154. $query[] = qsprintf(
  155. $conn_r,
  156. 'SELECT %T.*, %s commitRef
  157. FROM %T WHERE repositoryID = %d
  158. AND commitIdentifier LIKE %>',
  159. $commit_table,
  160. $identifier,
  161. $commit_table,
  162. $repository_id,
  163. $identifier);
  164. }
  165. } else {
  166. $query[] = qsprintf(
  167. $conn_r,
  168. 'SELECT %T.*, commitIdentifier commitRef
  169. FROM %T WHERE repositoryID = %d
  170. AND commitIdentifier IN (%Ls)',
  171. $commit_table,
  172. $commit_table,
  173. $repository_id,
  174. $identifiers);
  175. }
  176. }
  177. return queryfx_all(
  178. $conn_r,
  179. '%Q',
  180. implode(' UNION ALL ', $query));
  181. }
  182. /**
  183. * Enhance the commit list with RepositoryCommitData information.
  184. */
  185. private function addRepositoryCommitDataInformation(array $commits) {
  186. $commit_ids = ipull($commits, 'commitID');
  187. $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
  188. 'commitID in (%Ld)',
  189. $commit_ids);
  190. $data = mpull($data, null, 'getCommitID');
  191. foreach ($commits as $name => $commit) {
  192. if (isset($data[$commit['commitID']])) {
  193. $dobj = $data[$commit['commitID']];
  194. $commits[$name] += array(
  195. 'commitMessage' => $dobj->getCommitMessage(),
  196. 'commitDetails' => $dobj->getCommitDetails(),
  197. );
  198. }
  199. // Remove this information so we don't expose it via the API since
  200. // external services shouldn't be storing internal Commit IDs.
  201. unset($commits[$name]['commitID']);
  202. }
  203. return $commits;
  204. }
  205. /**
  206. * Enhance the commit list with Differential information.
  207. */
  208. private function addDifferentialInformation(array $commits) {
  209. $commit_phids = ipull($commits, 'commitPHID');
  210. $rev_conn_r = id(new DifferentialRevision())->establishConnection('r');
  211. $revs = queryfx_all(
  212. $rev_conn_r,
  213. 'SELECT r.id id, r.phid phid, c.commitPHID commitPHID FROM %T r JOIN %T c
  214. ON r.id = c.revisionID
  215. WHERE c.commitPHID in (%Ls)',
  216. id(new DifferentialRevision())->getTableName(),
  217. DifferentialRevision::TABLE_COMMIT,
  218. $commit_phids);
  219. $revs = ipull($revs, null, 'commitPHID');
  220. foreach ($commits as $name => $commit) {
  221. if (isset($revs[$commit['commitPHID']])) {
  222. $rev = $revs[$commit['commitPHID']];
  223. $commits[$name] += array(
  224. 'differentialRevisionID' => 'D'.$rev['id'],
  225. 'differentialRevisionPHID' => $rev['phid'],
  226. );
  227. }
  228. }
  229. return $commits;
  230. }
  231. }