2013-11-04 14:13:07 -08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolves references (like short commit names, branch names, tag names, etc.)
|
|
|
|
* into canonical, stable commit identifiers. This query works for all
|
|
|
|
* repository types.
|
2015-01-23 13:31:17 -08:00
|
|
|
*
|
|
|
|
* 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.
|
2013-11-04 14:13:07 -08:00
|
|
|
*/
|
|
|
|
final class DiffusionLowLevelResolveRefsQuery
|
|
|
|
extends DiffusionLowLevelQuery {
|
|
|
|
|
|
|
|
private $refs;
|
2015-04-27 03:51:53 -07:00
|
|
|
private $types;
|
2013-11-04 14:13:07 -08:00
|
|
|
|
|
|
|
public function withRefs(array $refs) {
|
|
|
|
$this->refs = $refs;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-04-27 03:51:53 -07:00
|
|
|
public function withTypes(array $types) {
|
|
|
|
$this->types = $types;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-01-16 06:56:32 +11:00
|
|
|
protected function executeQuery() {
|
2013-11-04 14:13:07 -08:00
|
|
|
if (!$this->refs) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2016-05-10 07:13:44 -07:00
|
|
|
$repository = $this->getRepository();
|
|
|
|
if (!$repository->hasLocalWorkingCopy()) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2013-11-04 14:13:07 -08:00
|
|
|
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:
|
2015-05-22 17:27:56 +10:00
|
|
|
throw new Exception(pht('Unsupported repository type!'));
|
2013-11-04 14:13:07 -08:00
|
|
|
}
|
|
|
|
|
2015-04-27 03:51:53 -07:00
|
|
|
if ($this->types !== null) {
|
|
|
|
$result = $this->filterRefsByType($result, $this->types);
|
|
|
|
}
|
|
|
|
|
2013-11-04 14:13:07 -08:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function resolveGitRefs() {
|
|
|
|
$repository = $this->getRepository();
|
|
|
|
|
2015-04-29 13:21:12 -07:00
|
|
|
$unresolved = array_fuse($this->refs);
|
|
|
|
$results = array();
|
|
|
|
|
|
|
|
// First, resolve branches and tags.
|
|
|
|
$ref_map = id(new DiffusionLowLevelGitRefQuery())
|
|
|
|
->setRepository($repository)
|
Version clustered, observed repositories in a reasonable way (by largest discovered HEAD)
Summary:
Ref T4292. For hosted, clustered repositories we have a good way to increment the internal version of the repository: every time a user pushes something, we increment the version by 1.
We don't have a great way to do this for observed/remote repositories because when we `git fetch` we might get nothing, or we might get some changes, and we can't easily tell //what// changes we got.
For example, if we see that another node is at "version 97", and we do a fetch and see some changes, we don't know if we're in sync with them (i.e., also at "version 97") or ahead of them (at "version 98").
This implements a simple way to version an observed repository:
- Take the head of every branch/tag.
- Look them up.
- Pick the biggest internal ID number.
This will work //except// when branches are deleted, which could cause the version to go backward if the "biggest commit" is the one that was deleted. This should be OK, since it's rare and the effects are minor and the repository will "self-heal" on the next actual push.
Test Plan:
- Created an observed repository.
- Ran `bin/repository update` and observed a sensible version number appear in the version table.
- Pushed to the remote, did another update, saw a sensible update.
- Did an update with no push, saw no effect on version number.
- Toggled repository to hosted, saw the version reset.
- Simulated read traffic to out-of-sync node, saw it do a remote fetch.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4292
Differential Revision: https://secure.phabricator.com/D15986
2016-05-27 06:21:19 -07:00
|
|
|
->withRefTypes(
|
|
|
|
array(
|
|
|
|
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
|
|
|
|
PhabricatorRepositoryRefCursor::TYPE_TAG,
|
|
|
|
))
|
2015-04-29 13:21:12 -07:00
|
|
|
->execute();
|
|
|
|
$ref_map = mgroup($ref_map, 'getShortName');
|
|
|
|
|
|
|
|
$tag_prefix = 'refs/tags/';
|
|
|
|
foreach ($unresolved 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^^^".
|
2013-11-04 14:13:07 -08:00
|
|
|
$future = $repository->getLocalCommandFuture('cat-file --batch-check');
|
2015-04-29 13:21:12 -07:00
|
|
|
$future->write(implode("\n", $unresolved));
|
2013-11-04 14:13:07 -08:00
|
|
|
list($stdout) = $future->resolvex();
|
|
|
|
|
|
|
|
$lines = explode("\n", rtrim($stdout, "\n"));
|
2015-04-29 13:21:12 -07:00
|
|
|
if (count($lines) !== count($unresolved)) {
|
2015-05-22 17:27:56 +10:00
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Unexpected line count from `%s`!',
|
|
|
|
'git cat-file'));
|
2013-11-04 14:13:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
$hits = array();
|
|
|
|
$tags = array();
|
|
|
|
|
2015-04-29 13:21:12 -07:00
|
|
|
$lines = array_combine($unresolved, $lines);
|
2013-11-04 14:13:07 -08:00
|
|
|
foreach ($lines as $ref => $line) {
|
|
|
|
$parts = explode(' ', $line);
|
|
|
|
if (count($parts) < 2) {
|
2015-05-22 17:27:56 +10:00
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Failed to parse `%s` output: %s',
|
|
|
|
'git cat-file',
|
|
|
|
$line));
|
2013-11-04 14:13:07 -08:00
|
|
|
}
|
|
|
|
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(
|
2015-05-22 17:27:56 +10:00
|
|
|
pht(
|
|
|
|
'Unexpected object type from `%s`: %s',
|
|
|
|
'git cat-file',
|
|
|
|
$line));
|
2013-11-04 14:13:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
$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)
|
2016-12-14 14:41:26 -05:00
|
|
|
->withRefTypes(
|
|
|
|
array(
|
|
|
|
PhabricatorRepositoryRefCursor::TYPE_TAG,
|
|
|
|
))
|
2013-11-04 14:13:07 -08:00
|
|
|
->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') {
|
|
|
|
$alternate = $identifier;
|
|
|
|
$identifier = idx($tag_map, $ref);
|
|
|
|
if (!$identifier) {
|
2015-05-22 17:27:56 +10:00
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
"Failed to look up tag '%s'!",
|
|
|
|
$ref));
|
2013-11-04 14:13:07 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = array(
|
|
|
|
'type' => $type,
|
|
|
|
'identifier' => $identifier,
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($alternate !== null) {
|
|
|
|
$result['alternate'] = $alternate;
|
|
|
|
}
|
|
|
|
|
|
|
|
$results[$ref][] = $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function resolveMercurialRefs() {
|
|
|
|
$repository = $this->getRepository();
|
|
|
|
|
Improve low-level branch resolution in Mercurial
Summary:
Ref T7100. Ref T7108. Ref T6160. Several issues:
- High load for mercurial repositories with huge numbers of branches (T7108).
- In Mercurial, we resolve refs individually (one `hg` call per ref).
- Each repository update also updates all refs, which requires resolving all of them.
- For repositories with a huge number of branches,
- We don't distinguish between closed branches (a Mercurial-only concept) and open branches (T6160).
- In Git, when a branch is merged, it ceases to exist.
- In Mercurial, when a branch is merged, it still exists, it's just "closed". Normally, no one cares about these branches.
- In the low-level query, correctly identify which refs we resolve as branches.
- In the low-level query, correctly mark closed branches as closed.
- This marginally improves ref handling in general (see T7100).
Test Plan:
{F384366}
{F384367}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6160, T7108, T7100
Differential Revision: https://secure.phabricator.com/D12548
2015-04-27 03:50:20 -07:00
|
|
|
// 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.
|
2015-04-27 03:50:55 -07:00
|
|
|
$branches = id(new DiffusionLowLevelMercurialBranchesQuery())
|
|
|
|
->setRepository($repository)
|
|
|
|
->executeQuery();
|
|
|
|
|
|
|
|
$branches = mgroup($branches, 'getShortName');
|
Improve low-level branch resolution in Mercurial
Summary:
Ref T7100. Ref T7108. Ref T6160. Several issues:
- High load for mercurial repositories with huge numbers of branches (T7108).
- In Mercurial, we resolve refs individually (one `hg` call per ref).
- Each repository update also updates all refs, which requires resolving all of them.
- For repositories with a huge number of branches,
- We don't distinguish between closed branches (a Mercurial-only concept) and open branches (T6160).
- In Git, when a branch is merged, it ceases to exist.
- In Mercurial, when a branch is merged, it still exists, it's just "closed". Normally, no one cares about these branches.
- In the low-level query, correctly identify which refs we resolve as branches.
- In the low-level query, correctly mark closed branches as closed.
- This marginally improves ref handling in general (see T7100).
Test Plan:
{F384366}
{F384367}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6160, T7108, T7100
Differential Revision: https://secure.phabricator.com/D12548
2015-04-27 03:50:20 -07:00
|
|
|
|
|
|
|
$results = array();
|
|
|
|
$unresolved = $this->refs;
|
|
|
|
foreach ($unresolved as $key => $ref) {
|
2015-04-27 03:50:55 -07:00
|
|
|
if (empty($branches[$ref])) {
|
Improve low-level branch resolution in Mercurial
Summary:
Ref T7100. Ref T7108. Ref T6160. Several issues:
- High load for mercurial repositories with huge numbers of branches (T7108).
- In Mercurial, we resolve refs individually (one `hg` call per ref).
- Each repository update also updates all refs, which requires resolving all of them.
- For repositories with a huge number of branches,
- We don't distinguish between closed branches (a Mercurial-only concept) and open branches (T6160).
- In Git, when a branch is merged, it ceases to exist.
- In Mercurial, when a branch is merged, it still exists, it's just "closed". Normally, no one cares about these branches.
- In the low-level query, correctly identify which refs we resolve as branches.
- In the low-level query, correctly mark closed branches as closed.
- This marginally improves ref handling in general (see T7100).
Test Plan:
{F384366}
{F384367}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6160, T7108, T7100
Differential Revision: https://secure.phabricator.com/D12548
2015-04-27 03:50:20 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-04-27 03:50:55 -07:00
|
|
|
foreach ($branches[$ref] as $branch) {
|
|
|
|
$fields = $branch->getRawFields();
|
|
|
|
|
|
|
|
$results[$ref][] = array(
|
Improve low-level branch resolution in Mercurial
Summary:
Ref T7100. Ref T7108. Ref T6160. Several issues:
- High load for mercurial repositories with huge numbers of branches (T7108).
- In Mercurial, we resolve refs individually (one `hg` call per ref).
- Each repository update also updates all refs, which requires resolving all of them.
- For repositories with a huge number of branches,
- We don't distinguish between closed branches (a Mercurial-only concept) and open branches (T6160).
- In Git, when a branch is merged, it ceases to exist.
- In Mercurial, when a branch is merged, it still exists, it's just "closed". Normally, no one cares about these branches.
- In the low-level query, correctly identify which refs we resolve as branches.
- In the low-level query, correctly mark closed branches as closed.
- This marginally improves ref handling in general (see T7100).
Test Plan:
{F384366}
{F384367}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6160, T7108, T7100
Differential Revision: https://secure.phabricator.com/D12548
2015-04-27 03:50:20 -07:00
|
|
|
'type' => 'branch',
|
2015-04-27 03:50:55 -07:00
|
|
|
'identifier' => $branch->getCommitIdentifier(),
|
|
|
|
'closed' => idx($fields, 'closed', false),
|
Improve low-level branch resolution in Mercurial
Summary:
Ref T7100. Ref T7108. Ref T6160. Several issues:
- High load for mercurial repositories with huge numbers of branches (T7108).
- In Mercurial, we resolve refs individually (one `hg` call per ref).
- Each repository update also updates all refs, which requires resolving all of them.
- For repositories with a huge number of branches,
- We don't distinguish between closed branches (a Mercurial-only concept) and open branches (T6160).
- In Git, when a branch is merged, it ceases to exist.
- In Mercurial, when a branch is merged, it still exists, it's just "closed". Normally, no one cares about these branches.
- In the low-level query, correctly identify which refs we resolve as branches.
- In the low-level query, correctly mark closed branches as closed.
- This marginally improves ref handling in general (see T7100).
Test Plan:
{F384366}
{F384367}
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6160, T7108, T7100
Differential Revision: https://secure.phabricator.com/D12548
2015-04-27 03:50:20 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2013-11-04 14:13:07 -08:00
|
|
|
$futures[$ref] = $repository->getLocalCommandFuture(
|
|
|
|
'log --template=%s --rev %s',
|
|
|
|
'{node}',
|
|
|
|
hgsprintf('%s', $ref));
|
|
|
|
}
|
|
|
|
|
2014-12-30 23:13:38 +11:00
|
|
|
foreach (new FutureIterator($futures) as $ref => $future) {
|
2013-11-04 14:13:07 -08:00
|
|
|
try {
|
|
|
|
list($stdout) = $future->resolvex();
|
|
|
|
} catch (CommandException $ex) {
|
2017-02-18 09:24:56 +00:00
|
|
|
if (preg_match('/ambiguous identifier/', $ex->getStderr())) {
|
2013-11-04 14:13:07 -08:00
|
|
|
// 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;
|
|
|
|
}
|
2017-02-18 09:24:56 +00:00
|
|
|
if (preg_match('/unknown revision/', $ex->getStderr())) {
|
2015-04-28 08:56:16 -07:00
|
|
|
// No matches for this ref.
|
|
|
|
continue;
|
|
|
|
}
|
2013-11-04 14:13:07 -08:00
|
|
|
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() {
|
2015-01-23 13:31:17 -08:00
|
|
|
// 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();
|
2013-11-04 14:13:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|