[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/diffusion/query/lowlevel/ -> DiffusionLowLevelResolveRefsQuery.php (source)

   1  <?php
   2  
   3  /**
   4   * Resolves references (like short commit names, branch names, tag names, etc.)
   5   * into canonical, stable commit identifiers. This query works for all
   6   * repository types.
   7   */
   8  final class DiffusionLowLevelResolveRefsQuery
   9    extends DiffusionLowLevelQuery {
  10  
  11    private $refs;
  12  
  13    public function withRefs(array $refs) {
  14      $this->refs = $refs;
  15      return $this;
  16    }
  17  
  18    public function executeQuery() {
  19      if (!$this->refs) {
  20        return array();
  21      }
  22  
  23      switch ($this->getRepository()->getVersionControlSystem()) {
  24        case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
  25          $result = $this->resolveGitRefs();
  26          break;
  27        case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
  28          $result = $this->resolveMercurialRefs();
  29          break;
  30        case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
  31          $result = $this->resolveSubversionRefs();
  32          break;
  33        default:
  34          throw new Exception('Unsupported repository type!');
  35      }
  36  
  37      return $result;
  38    }
  39  
  40    private function resolveGitRefs() {
  41      $repository = $this->getRepository();
  42  
  43      $future = $repository->getLocalCommandFuture('cat-file --batch-check');
  44      $future->write(implode("\n", $this->refs));
  45      list($stdout) = $future->resolvex();
  46  
  47      $lines = explode("\n", rtrim($stdout, "\n"));
  48      if (count($lines) !== count($this->refs)) {
  49        throw new Exception('Unexpected line count from `git cat-file`!');
  50      }
  51  
  52      $hits = array();
  53      $tags = array();
  54  
  55      $lines = array_combine($this->refs, $lines);
  56      foreach ($lines as $ref => $line) {
  57        $parts = explode(' ', $line);
  58        if (count($parts) < 2) {
  59          throw new Exception("Failed to parse `git cat-file` output: {$line}");
  60        }
  61        list($identifier, $type) = $parts;
  62  
  63        if ($type == 'missing') {
  64          // This is either an ambiguous reference which resolves to several
  65          // objects, or an invalid reference. For now, always treat it as
  66          // invalid. It would be nice to resolve all possibilities for
  67          // ambiguous references at some point, although the strategy for doing
  68          // so isn't clear to me.
  69          continue;
  70        }
  71  
  72        switch ($type) {
  73          case 'commit':
  74            break;
  75          case 'tag':
  76            $tags[] = $identifier;
  77            break;
  78          default:
  79            throw new Exception(
  80              "Unexpected object type from `git cat-file`: {$line}");
  81        }
  82  
  83        $hits[] = array(
  84          'ref' => $ref,
  85          'type' => $type,
  86          'identifier' => $identifier,
  87        );
  88      }
  89  
  90      $tag_map = array();
  91      if ($tags) {
  92        // If some of the refs were tags, just load every tag in order to figure
  93        // out which commits they map to. This might be somewhat inefficient in
  94        // repositories with a huge number of tags.
  95        $tag_refs = id(new DiffusionLowLevelGitRefQuery())
  96          ->setRepository($repository)
  97          ->withIsTag(true)
  98          ->executeQuery();
  99        foreach ($tag_refs as $tag_ref) {
 100          $tag_map[$tag_ref->getShortName()] = $tag_ref->getCommitIdentifier();
 101        }
 102      }
 103  
 104      $results = array();
 105      foreach ($hits as $hit) {
 106        $type = $hit['type'];
 107        $ref = $hit['ref'];
 108  
 109        $alternate = null;
 110        if ($type == 'tag') {
 111          $alternate = $identifier;
 112          $identifier = idx($tag_map, $ref);
 113          if (!$identifier) {
 114            throw new Exception("Failed to look up tag '{$ref}'!");
 115          }
 116        }
 117  
 118        $result = array(
 119          'type' => $type,
 120          'identifier' => $identifier,
 121        );
 122  
 123        if ($alternate !== null) {
 124          $result['alternate'] = $alternate;
 125        }
 126  
 127        $results[$ref][] = $result;
 128      }
 129  
 130      return $results;
 131    }
 132  
 133    private function resolveMercurialRefs() {
 134      $repository = $this->getRepository();
 135  
 136      $futures = array();
 137      foreach ($this->refs as $ref) {
 138        $futures[$ref] = $repository->getLocalCommandFuture(
 139          'log --template=%s --rev %s',
 140          '{node}',
 141          hgsprintf('%s', $ref));
 142      }
 143  
 144      $results = array();
 145      foreach (Futures($futures) as $ref => $future) {
 146        try {
 147          list($stdout) = $future->resolvex();
 148        } catch (CommandException $ex) {
 149          if (preg_match('/ambiguous identifier/', $ex->getStdErr())) {
 150            // This indicates that the ref ambiguously matched several things.
 151            // Eventually, it would be nice to return all of them, but it is
 152            // unclear how to best do that. For now, treat it as a miss instead.
 153            continue;
 154          }
 155          throw $ex;
 156        }
 157  
 158        // It doesn't look like we can figure out the type (commit/branch/rev)
 159        // from this output very easily. For now, just call everything a commit.
 160        $type = 'commit';
 161  
 162        $results[$ref][] = array(
 163          'type' => $type,
 164          'identifier' => trim($stdout),
 165        );
 166      }
 167  
 168      return $results;
 169    }
 170  
 171    private function resolveSubversionRefs() {
 172      $repository = $this->getRepository();
 173  
 174      $max_commit = id(new PhabricatorRepositoryCommit())
 175        ->loadOneWhere(
 176          'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1',
 177          $repository->getID());
 178      if (!$max_commit) {
 179        // This repository is empty or hasn't parsed yet, so none of the refs are
 180        // going to resolve.
 181        return array();
 182      }
 183  
 184      $max_commit_id = (int)$max_commit->getCommitIdentifier();
 185  
 186      $results = array();
 187      foreach ($this->refs as $ref) {
 188        if ($ref == 'HEAD') {
 189          // Resolve "HEAD" to mean "the most recent commit".
 190          $results[$ref][] = array(
 191            'type' => 'commit',
 192            'identifier' => $max_commit_id,
 193          );
 194          continue;
 195        }
 196  
 197        if (!preg_match('/^\d+$/', $ref)) {
 198          // This ref is non-numeric, so it doesn't resolve to anything.
 199          continue;
 200        }
 201  
 202        // Resolve other commits if we can deduce their existence.
 203  
 204        // TODO: When we import only part of a repository, we won't necessarily
 205        // have all of the smaller commits. Should we fail to resolve them here
 206        // for repositories with a subpath? It might let us simplify other things
 207        // elsewhere.
 208        if ((int)$ref <= $max_commit_id) {
 209          $results[$ref][] = array(
 210            'type' => 'commit',
 211            'identifier' => (int)$ref,
 212          );
 213        }
 214      }
 215  
 216      return $results;
 217    }
 218  
 219  }


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