Something went wrong. Try again.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
Something went wrong. Try again.
11 kB · 392 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393<?php
/** * Resolves references (like short commit names, branch names, tag names, etc.) * into canonical, stable commit identifiers. This query works for all * repository types. * * This query will always resolve refs which can be resolved, but may need to * perform VCS operations. A faster (but less complete) counterpart query is * available in @{class:DiffusionCachedResolveRefsQuery}; that query can * resolve most refs without VCS operations. */final class DiffusionLowLevelResolveRefsQuery extends DiffusionLowLevelQuery {
private $refs; private $types;
public function withRefs(array $refs) { $this->refs = $refs; return $this; }
public function withTypes(array $types) { $this->types = $types; return $this; }
protected function executeQuery() { if (!$this->refs) { return array(); }
$repository = $this->getRepository(); if (!$repository->hasLocalWorkingCopy()) { return array(); }
switch ($this->getRepository()->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $result = $this->resolveGitRefs(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $result = $this->resolveMercurialRefs(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $result = $this->resolveSubversionRefs(); break; default: throw new Exception(pht('Unsupported repository type!')); }
if ($this->types !== null) { $result = $this->filterRefsByType($result, $this->types); }
return $result; }
private function resolveGitRefs() { $repository = $this->getRepository();
$unresolved = array_fuse($this->refs); $results = array();
$possible_symbols = array(); foreach ($unresolved as $ref) {
// See T13647. If this symbol is exactly 40 hex characters long, it may // never resolve as a branch or tag name. Filter these symbols out for // consistency with Git behavior -- and to avoid an expensive // "git for-each-ref" when resolving only commit hashes, which happens // during repository updates.
if (preg_match('(^[a-f0-9]{40}\z)', $ref)) { continue; }
$possible_symbols[$ref] = $ref; }
// First, resolve branches and tags. if ($possible_symbols) { $ref_map = id(new DiffusionLowLevelGitRefQuery()) ->setRepository($repository) ->withRefTypes( array( PhabricatorRepositoryRefCursor::TYPE_BRANCH, PhabricatorRepositoryRefCursor::TYPE_TAG, )) ->execute(); $ref_map = mgroup($ref_map, 'getShortName');
$tag_prefix = 'refs/tags/'; foreach ($possible_symbols as $ref) { if (empty($ref_map[$ref])) { continue; }
foreach ($ref_map[$ref] as $result) { $fields = $result->getRawFields(); $objectname = idx($fields, 'refname'); if (!strncmp($objectname, $tag_prefix, strlen($tag_prefix))) { $type = 'tag'; } else { $type = 'branch'; }
$info = array( 'type' => $type, 'identifier' => $result->getCommitIdentifier(), );
if ($type == 'tag') { $alternate = idx($fields, 'objectname'); if ($alternate) { $info['alternate'] = $alternate; } }
$results[$ref][] = $info; }
unset($unresolved[$ref]); } }
// If we resolved everything, we're done. if (!$unresolved) { return $results; }
// Try to resolve anything else. This stuff either doesn't exist or is // some ref like "HEAD^^^". $future = $repository->getLocalCommandFuture('cat-file --batch-check'); $future->write(implode("\n", $unresolved)); list($stdout) = $future->resolvex();
$lines = explode("\n", rtrim($stdout, "\n")); if (count($lines) !== count($unresolved)) { throw new Exception( pht( 'Unexpected line count from `%s` in %s!', 'git cat-file', $repository->getMonogram())); }
$hits = array(); $tags = array();
$lines = array_combine($unresolved, $lines); foreach ($lines as $ref => $line) { $parts = explode(' ', $line); if (count($parts) < 2) { throw new Exception( pht( 'Failed to parse `%s` output in %s: %s', 'git cat-file', $repository->getMonogram(), $line)); } list($identifier, $type) = $parts;
if ($type == 'missing') { // This is either an ambiguous reference which resolves to several // objects, or an invalid reference. For now, always treat it as // invalid. It would be nice to resolve all possibilities for // ambiguous references at some point, although the strategy for doing // so isn't clear to me. continue; }
switch ($type) { case 'commit': break; case 'tag': $tags[] = $identifier; break; default: throw new Exception( pht( 'Unexpected object type from `%s` in %s: %s', 'git cat-file', $repository->getMonogram(), $line)); }
$hits[] = array( 'ref' => $ref, 'type' => $type, 'identifier' => $identifier, ); }
$tag_map = array(); if ($tags) { // If some of the refs were tags, just load every tag in order to figure // out which commits they map to. This might be somewhat inefficient in // repositories with a huge number of tags. $tag_refs = id(new DiffusionLowLevelGitRefQuery()) ->setRepository($repository) ->withRefTypes( array( PhabricatorRepositoryRefCursor::TYPE_TAG, )) ->executeQuery(); foreach ($tag_refs as $tag_ref) { $tag_map[$tag_ref->getShortName()] = $tag_ref->getCommitIdentifier(); } }
$results = array(); foreach ($hits as $hit) { $type = $hit['type']; $ref = $hit['ref'];
$alternate = null; if ($type == 'tag') { $tag_identifier = idx($tag_map, $ref); if ($tag_identifier === null) { // This can happen when we're asked to resolve the hash of a "tag" // object created with "git tag --annotate" that isn't currently // reachable from any ref. Just leave things as they are. } else { // Otherwise, we have a normal named tag. $alternate = $identifier; $identifier = $tag_identifier; } }
$result = array( 'type' => $type, 'identifier' => $identifier, );
if ($alternate !== null) { $result['alternate'] = $alternate; }
$results[$ref][] = $result; }
return $results; }
private function resolveMercurialRefs() { $repository = $this->getRepository();
// First, pull all of the branch heads in the repository. Doing this in // bulk is much faster than querying each individual head if we're // checking even a small number of refs. $branches = id(new DiffusionLowLevelMercurialBranchesQuery()) ->setRepository($repository) ->executeQuery();
$branches = mgroup($branches, 'getShortName');
$results = array(); $unresolved = $this->refs; foreach ($unresolved as $key => $ref) { if (empty($branches[$ref])) { continue; }
foreach ($branches[$ref] as $branch) { $fields = $branch->getRawFields();
$results[$ref][] = array( 'type' => 'branch', 'identifier' => $branch->getCommitIdentifier(), 'closed' => idx($fields, 'closed', false), ); }
unset($unresolved[$key]); }
if (!$unresolved) { return $results; }
// If some of the refs look like hashes, try to bulk resolve them. This // workflow happens via RefEngine and bulk resolution is dramatically // faster than individual resolution. See PHI158.
$hashlike = array(); foreach ($unresolved as $key => $ref) { if (preg_match('/^[a-f0-9]{40}\z/', $ref)) { $hashlike[$key] = $ref; } }
if (count($hashlike) > 1) { $hashlike_map = array();
$hashlike_groups = array_chunk($hashlike, 64, true); foreach ($hashlike_groups as $hashlike_group) { $hashlike_arg = array(); foreach ($hashlike_group as $hashlike_ref) { $hashlike_arg[] = hgsprintf('%s', $hashlike_ref); } $hashlike_arg = '('.implode(' or ', $hashlike_arg).')';
list($err, $refs) = $repository->execLocalCommand( 'log --template=%s --rev %s', '{node}\n', $hashlike_arg); if ($err) { // NOTE: If any ref fails to resolve, Mercurial will exit with an // error. We just give up on the whole group and resolve it // individually below. In theory, we could split it into subgroups // but the pathway where this bulk resolution matters rarely tries // to resolve missing refs (see PHI158). continue; }
$refs = phutil_split_lines($refs, false);
foreach ($refs as $ref) { $hashlike_map[$ref] = true; } }
foreach ($unresolved as $key => $ref) { if (!isset($hashlike_map[$ref])) { continue; }
$results[$ref][] = array( 'type' => 'commit', 'identifier' => $ref, );
unset($unresolved[$key]); } }
if (!$unresolved) { return $results; }
// If we still have unresolved refs (which might be things like "tip"), // try to resolve them individually.
$futures = array(); foreach ($unresolved as $ref) { $futures[$ref] = $repository->getLocalCommandFuture( 'log --template=%s --rev %s', '{node}', hgsprintf('%s', $ref)); }
foreach (new FutureIterator($futures) as $ref => $future) { try { list($stdout) = $future->resolvex(); } catch (CommandException $ex) { if (preg_match('/ambiguous identifier/', $ex->getStderr())) { // This indicates that the ref ambiguously matched several things. // Eventually, it would be nice to return all of them, but it is // unclear how to best do that. For now, treat it as a miss instead. continue; } if (preg_match('/unknown revision/', $ex->getStderr())) { // No matches for this ref. continue; } throw $ex; }
// It doesn't look like we can figure out the type (commit/branch/rev) // from this output very easily. For now, just call everything a commit. $type = 'commit';
$results[$ref][] = array( 'type' => $type, 'identifier' => trim($stdout), ); }
return $results; }
private function resolveSubversionRefs() { // We don't have any VCS logic for Subversion, so just use the cached // query. return id(new DiffusionCachedResolveRefsQuery()) ->setRepository($this->getRepository()) ->withRefs($this->refs) ->execute(); }
}