[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/diffusion/conduit/ -> DiffusionGetCommitsConduitAPIMethod.php (source)

   1  <?php
   2  
   3  final class DiffusionGetCommitsConduitAPIMethod
   4    extends DiffusionConduitAPIMethod {
   5  
   6    public function getAPIMethodName() {
   7      return 'diffusion.getcommits';
   8    }
   9  
  10    public function getMethodDescription() {
  11      return pht('Retrieve Diffusion commit information.');
  12    }
  13  
  14    public function getMethodStatus() {
  15      return self::METHOD_STATUS_DEPRECATED;
  16    }
  17  
  18    public function getMethodStatusDescription() {
  19      return pht('Obsoleted by diffusion.querycommits.');
  20    }
  21  
  22    public function defineParamTypes() {
  23      return array(
  24        'commits' => 'required list<string>',
  25      );
  26    }
  27  
  28    public function defineReturnType() {
  29      return 'nonempty list<dict<string, wild>>';
  30    }
  31  
  32    public function defineErrorTypes() {
  33      return array();
  34    }
  35  
  36    protected function execute(ConduitAPIRequest $request) {
  37      $results = array();
  38  
  39      $commits = $request->getValue('commits');
  40      $commits = array_fill_keys($commits, array());
  41      foreach ($commits as $name => $info) {
  42        $matches = null;
  43        if (!preg_match('/^r([A-Z]+)([0-9a-f]+)\z/', $name, $matches)) {
  44          $results[$name] = array(
  45            'error' => 'ERR-UNPARSEABLE',
  46          );
  47          unset($commits[$name]);
  48          continue;
  49        }
  50        $commits[$name] = array(
  51          'callsign'          => $matches[1],
  52          'commitIdentifier'  => $matches[2],
  53        );
  54      }
  55  
  56      if (!$commits) {
  57        return $results;
  58      }
  59  
  60      $callsigns = ipull($commits, 'callsign');
  61      $callsigns = array_unique($callsigns);
  62      $repos = id(new PhabricatorRepositoryQuery())
  63        ->setViewer($request->getUser())
  64        ->withCallsigns($callsigns)
  65        ->execute();
  66      $repos = mpull($repos, null, 'getCallsign');
  67  
  68      foreach ($commits as $name => $info) {
  69        $repo = idx($repos, $info['callsign']);
  70        if (!$repo) {
  71          $results[$name] = $info + array(
  72            'error' => 'ERR-UNKNOWN-REPOSITORY',
  73          );
  74          unset($commits[$name]);
  75          continue;
  76        }
  77        $commits[$name] += array(
  78          'repositoryPHID' => $repo->getPHID(),
  79          'repositoryID' => $repo->getID(),
  80        );
  81      }
  82  
  83      if (!$commits) {
  84        return $results;
  85      }
  86  
  87      // Execute a complicated query to figure out the primary commit information
  88      // for each referenced commit.
  89      $cdata = $this->queryCommitInformation($commits, $repos);
  90  
  91      // We've built the queries so that each row also has the identifier we used
  92      // to select it, which might be a git prefix rather than a full identifier.
  93      $ref_map = ipull($cdata, 'commitIdentifier', 'commitRef');
  94  
  95      $cobjs = id(new PhabricatorRepositoryCommit())->loadAllFromArray($cdata);
  96      $cobjs = mgroup($cobjs, 'getRepositoryID', 'getCommitIdentifier');
  97      foreach ($commits as $name => $commit) {
  98  
  99        // Expand short git names into full identifiers. For SVN this map is just
 100        // the identity.
 101        $full_identifier = idx($ref_map, $commit['commitIdentifier']);
 102  
 103        $repo_id = $commit['repositoryID'];
 104        unset($commits[$name]['repositoryID']);
 105  
 106        if (empty($full_identifier) ||
 107            empty($cobjs[$commit['repositoryID']][$full_identifier])) {
 108          $results[$name] = $commit + array(
 109            'error' => 'ERR-UNKNOWN-COMMIT',
 110          );
 111          unset($commits[$name]);
 112          continue;
 113        }
 114  
 115        $cobj_arr = $cobjs[$commit['repositoryID']][$full_identifier];
 116        $cobj = head($cobj_arr);
 117  
 118        $commits[$name] += array(
 119          'epoch'             => $cobj->getEpoch(),
 120          'commitPHID'        => $cobj->getPHID(),
 121          'commitID'          => $cobj->getID(),
 122        );
 123  
 124        // Upgrade git short references into full commit identifiers.
 125        $identifier = $cobj->getCommitIdentifier();
 126        $commits[$name]['commitIdentifier'] = $identifier;
 127  
 128        $callsign = $commits[$name]['callsign'];
 129        $uri = "/r{$callsign}{$identifier}";
 130        $commits[$name]['uri'] = PhabricatorEnv::getProductionURI($uri);
 131      }
 132  
 133      if (!$commits) {
 134        return $results;
 135      }
 136  
 137      $commits = $this->addRepositoryCommitDataInformation($commits);
 138      $commits = $this->addDifferentialInformation($commits);
 139      $commits = $this->addManiphestInformation($commits);
 140  
 141      foreach ($commits as $name => $commit) {
 142        $results[$name] = $commit;
 143      }
 144  
 145      return $results;
 146    }
 147  
 148    /**
 149     * Retrieve primary commit information for all referenced commits.
 150     */
 151    private function queryCommitInformation(array $commits, array $repos) {
 152      assert_instances_of($repos, 'PhabricatorRepository');
 153      $conn_r = id(new PhabricatorRepositoryCommit())->establishConnection('r');
 154      $repos = mpull($repos, null, 'getID');
 155  
 156      $groups = array();
 157      foreach ($commits as $name => $commit) {
 158        $groups[$commit['repositoryID']][] = $commit['commitIdentifier'];
 159      }
 160  
 161      // NOTE: MySQL goes crazy and does a massive table scan if we build a more
 162      // sensible version of this query. Make sure the query plan is OK if you
 163      // attempt to reduce the craziness here. METANOTE: The addition of prefix
 164      // selection for Git further complicates matters.
 165      $query = array();
 166      $commit_table = id(new PhabricatorRepositoryCommit())->getTableName();
 167  
 168      foreach ($groups as $repository_id => $identifiers) {
 169        $vcs = $repos[$repository_id]->getVersionControlSystem();
 170        $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
 171        if ($is_git) {
 172          foreach ($identifiers as $identifier) {
 173            if (strlen($identifier) < 7) {
 174              // Don't bother with silly stuff like 'rX2', which will select
 175              // 1/16th of all commits. Note that with length 7 we'll still get
 176              // collisions in repositories at the tens-of-thousands-of-commits
 177              // scale.
 178              continue;
 179            }
 180            $query[] = qsprintf(
 181              $conn_r,
 182              'SELECT %T.*, %s commitRef
 183                FROM %T WHERE repositoryID = %d
 184                AND commitIdentifier LIKE %>',
 185              $commit_table,
 186              $identifier,
 187              $commit_table,
 188              $repository_id,
 189              $identifier);
 190          }
 191        } else {
 192          $query[] = qsprintf(
 193            $conn_r,
 194            'SELECT %T.*, commitIdentifier commitRef
 195              FROM %T WHERE repositoryID = %d
 196              AND commitIdentifier IN (%Ls)',
 197            $commit_table,
 198            $commit_table,
 199            $repository_id,
 200            $identifiers);
 201        }
 202      }
 203  
 204      return queryfx_all(
 205        $conn_r,
 206        '%Q',
 207        implode(' UNION ALL ', $query));
 208    }
 209  
 210    /**
 211     * Enhance the commit list with RepositoryCommitData information.
 212     */
 213    private function addRepositoryCommitDataInformation(array $commits) {
 214      $commit_ids = ipull($commits, 'commitID');
 215  
 216      $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
 217        'commitID in (%Ld)',
 218        $commit_ids);
 219      $data = mpull($data, null, 'getCommitID');
 220  
 221      foreach ($commits as $name => $commit) {
 222        if (isset($data[$commit['commitID']])) {
 223          $dobj = $data[$commit['commitID']];
 224          $commits[$name] += array(
 225            'commitMessage' => $dobj->getCommitMessage(),
 226            'commitDetails' => $dobj->getCommitDetails(),
 227          );
 228        }
 229  
 230        // Remove this information so we don't expose it via the API since
 231        // external services shouldn't be storing internal Commit IDs.
 232        unset($commits[$name]['commitID']);
 233      }
 234  
 235      return $commits;
 236    }
 237  
 238    /**
 239     * Enhance the commit list with Differential information.
 240     */
 241    private function addDifferentialInformation(array $commits) {
 242      $commit_phids = ipull($commits, 'commitPHID');
 243  
 244      // TODO: (T603) This should be policy checked, either by moving to
 245      // DifferentialRevisionQuery or by doing a followup query to make sure
 246      // the matched objects are visible.
 247  
 248      $rev_conn_r = id(new DifferentialRevision())->establishConnection('r');
 249      $revs = queryfx_all(
 250        $rev_conn_r,
 251        'SELECT r.id id, r.phid phid, c.commitPHID commitPHID FROM %T r JOIN %T c
 252          ON r.id = c.revisionID
 253          WHERE c.commitPHID in (%Ls)',
 254        id(new DifferentialRevision())->getTableName(),
 255        DifferentialRevision::TABLE_COMMIT,
 256        $commit_phids);
 257  
 258      $revs = ipull($revs, null, 'commitPHID');
 259      foreach ($commits as $name => $commit) {
 260        if (isset($revs[$commit['commitPHID']])) {
 261          $rev = $revs[$commit['commitPHID']];
 262          $commits[$name] += array(
 263            'differentialRevisionID'    => 'D'.$rev['id'],
 264            'differentialRevisionPHID'  => $rev['phid'],
 265          );
 266        }
 267      }
 268  
 269      return $commits;
 270    }
 271  
 272    /**
 273     * Enhances the commits list with Maniphest information.
 274     */
 275    private function addManiphestInformation(array $commits) {
 276      $task_type = DiffusionCommitHasTaskEdgeType::EDGECONST;
 277  
 278      $commit_phids = ipull($commits, 'commitPHID');
 279  
 280      $edge_query = id(new PhabricatorEdgeQuery())
 281        ->withSourcePHIDs($commit_phids)
 282        ->withEdgeTypes(array($task_type));
 283  
 284      $edges = $edge_query->execute();
 285  
 286      foreach ($commits as $name => $commit) {
 287        $task_phids = $edge_query->getDestinationPHIDs(
 288          array($commit['commitPHID']),
 289          array($task_type));
 290  
 291        $commits[$name] += array(
 292          'taskPHIDs' => $task_phids,
 293        );
 294      }
 295  
 296      return $commits;
 297    }
 298  
 299  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1