Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php
index 751fbc4a95..1f17f5fe9f 100644
--- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php
+++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php
@@ -1,135 +1,152 @@
<?php
/**
* Execute and parse a low-level Git ref query using `git for-each-ref`. This
* is useful for returning a list of tags or branches.
- *
- *
*/
final class DiffusionLowLevelGitRefQuery extends DiffusionLowLevelQuery {
private $isTag;
private $isOriginBranch;
public function withIsTag($is_tag) {
$this->isTag = $is_tag;
return $this;
}
public function withIsOriginBranch($is_origin_branch) {
$this->isOriginBranch = $is_origin_branch;
return $this;
}
protected function executeQuery() {
$repository = $this->getRepository();
- if ($this->isTag && $this->isOriginBranch) {
- throw new Exception('Specify tags or origin branches, not both!');
- } else if ($this->isTag) {
- $prefix = 'refs/tags/';
- } else if ($this->isOriginBranch) {
+ $prefixes = array();
+
+ $any = ($this->isTag || $this->isOriginBranch);
+ if (!$any) {
+ throw new Exception(pht('Specify types of refs to query.'));
+ }
+
+ if ($this->isOriginBranch) {
if ($repository->isWorkingCopyBare()) {
$prefix = 'refs/heads/';
} else {
$remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE;
$prefix = 'refs/remotes/'.$remote.'/';
}
- } else {
- throw new Exception('Specify tags or origin branches!');
+ $prefixes[] = $prefix;
}
- $order = '-creatordate';
+ if ($this->isTag) {
+ $prefixes[] = 'refs/tags/';
+ }
- list($stdout) = $repository->execxLocalCommand(
- 'for-each-ref --sort=%s --format=%s %s',
- $order,
- $this->getFormatString(),
- $prefix);
+ $order = '-creatordate';
- $stdout = rtrim($stdout);
- if (!strlen($stdout)) {
- return array();
+ $futures = array();
+ foreach ($prefixes as $prefix) {
+ $futures[$prefix] = $repository->getLocalCommandFuture(
+ 'for-each-ref --sort=%s --format=%s %s',
+ $order,
+ $this->getFormatString(),
+ $prefix);
}
- // NOTE: Although git supports --count, we can't apply any offset or limit
- // logic until the very end because we may encounter a HEAD which we want
- // to discard.
+ // Resolve all the futures first. We want to iterate over them in prefix
+ // order, not resolution order.
+ foreach (new FutureIterator($futures) as $prefix => $future) {
+ $future->resolvex();
+ }
- $lines = explode("\n", $stdout);
$results = array();
- foreach ($lines as $line) {
- $fields = $this->extractFields($line);
-
- $creator = $fields['creator'];
- $matches = null;
- if (preg_match('/^(.*) ([0-9]+) ([0-9+-]+)$/', $creator, $matches)) {
- $fields['author'] = $matches[1];
- $fields['epoch'] = (int)$matches[2];
- } else {
- $fields['author'] = null;
- $fields['epoch'] = null;
- }
+ foreach ($futures as $prefix => $future) {
+ list($stdout) = $future->resolvex();
- $commit = nonempty($fields['*objectname'], $fields['objectname']);
-
- $short = substr($fields['refname'], strlen($prefix));
- if ($short == 'HEAD') {
+ $stdout = rtrim($stdout);
+ if (!strlen($stdout)) {
continue;
}
- $ref = id(new DiffusionRepositoryRef())
- ->setShortName($short)
- ->setCommitIdentifier($commit)
- ->setRawFields($fields);
-
- $results[] = $ref;
+ // NOTE: Although git supports --count, we can't apply any offset or
+ // limit logic until the very end because we may encounter a HEAD which
+ // we want to discard.
+
+ $lines = explode("\n", $stdout);
+ foreach ($lines as $line) {
+ $fields = $this->extractFields($line);
+
+ $creator = $fields['creator'];
+ $matches = null;
+ if (preg_match('/^(.*) ([0-9]+) ([0-9+-]+)$/', $creator, $matches)) {
+ $fields['author'] = $matches[1];
+ $fields['epoch'] = (int)$matches[2];
+ } else {
+ $fields['author'] = null;
+ $fields['epoch'] = null;
+ }
+
+ $commit = nonempty($fields['*objectname'], $fields['objectname']);
+
+ $short = substr($fields['refname'], strlen($prefix));
+ if ($short == 'HEAD') {
+ continue;
+ }
+
+ $ref = id(new DiffusionRepositoryRef())
+ ->setShortName($short)
+ ->setCommitIdentifier($commit)
+ ->setRawFields($fields);
+
+ $results[] = $ref;
+ }
}
return $results;
}
/**
* List of git `--format` fields we want to grab.
*/
private function getFields() {
return array(
'objectname',
'objecttype',
'refname',
'*objectname',
'*objecttype',
'subject',
'creator',
);
}
/**
* Get a string for `--format` which enumerates all the fields we want.
*/
private function getFormatString() {
$fields = $this->getFields();
foreach ($fields as $key => $field) {
$fields[$key] = '%('.$field.')';
}
return implode('%01', $fields);
}
/**
* Parse a line back into fields.
*/
private function extractFields($line) {
$fields = $this->getFields();
$parts = explode("\1", $line, count($fields));
$dict = array();
foreach ($fields as $index => $field) {
$dict[$field] = idx($parts, $index);
}
return $dict;
}
}
diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php
index 2fc03fb739..3926dbf398 100644
--- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php
+++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php
@@ -1,236 +1,285 @@
<?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();
}
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('Unsupported repository type!');
}
if ($this->types !== null) {
$result = $this->filterRefsByType($result, $this->types);
}
return $result;
}
private function resolveGitRefs() {
$repository = $this->getRepository();
- // TODO: When refs are ambiguous (for example, tags and branches with
- // the same name) this will only resolve one of them.
+ $unresolved = array_fuse($this->refs);
+ $results = array();
+
+ // First, resolve branches and tags.
+ $ref_map = id(new DiffusionLowLevelGitRefQuery())
+ ->setRepository($repository)
+ ->withIsTag(true)
+ ->withIsOriginBranch(true)
+ ->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^^^".
$future = $repository->getLocalCommandFuture('cat-file --batch-check');
- $future->write(implode("\n", $this->refs));
+ $future->write(implode("\n", $unresolved));
list($stdout) = $future->resolvex();
$lines = explode("\n", rtrim($stdout, "\n"));
- if (count($lines) !== count($this->refs)) {
+ if (count($lines) !== count($unresolved)) {
throw new Exception('Unexpected line count from `git cat-file`!');
}
$hits = array();
$tags = array();
- $lines = array_combine($this->refs, $lines);
+ $lines = array_combine($unresolved, $lines);
foreach ($lines as $ref => $line) {
$parts = explode(' ', $line);
if (count($parts) < 2) {
throw new Exception("Failed to parse `git cat-file` output: {$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(
"Unexpected object type from `git cat-file`: {$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)
->withIsTag(true)
->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) {
throw new Exception("Failed to look up tag '{$ref}'!");
}
}
$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 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();
}
}
diff --git a/src/applications/diffusion/request/DiffusionGitRequest.php b/src/applications/diffusion/request/DiffusionGitRequest.php
index b020bb4c4b..48d0761506 100644
--- a/src/applications/diffusion/request/DiffusionGitRequest.php
+++ b/src/applications/diffusion/request/DiffusionGitRequest.php
@@ -1,32 +1,23 @@
<?php
final class DiffusionGitRequest extends DiffusionRequest {
public function supportsBranches() {
return true;
}
protected function isStableCommit($symbol) {
return preg_match('/^[a-f0-9]{40}\z/', $symbol);
}
public function getBranch() {
if ($this->branch) {
return $this->branch;
}
if ($this->repository) {
return $this->repository->getDefaultBranch();
}
throw new Exception('Unable to determine branch!');
}
- protected function getResolvableBranchName($branch) {
- if ($this->repository->isWorkingCopyBare()) {
- return $branch;
- } else {
- $remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE;
- return $remote.'/'.$branch;
- }
- }
-
}
diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php
index f0f6d49c23..69ea4938af 100644
--- a/src/applications/diffusion/request/DiffusionRequest.php
+++ b/src/applications/diffusion/request/DiffusionRequest.php
@@ -1,849 +1,845 @@
<?php
/**
* Contains logic to parse Diffusion requests, which have a complicated URI
* structure.
*
* @task new Creating Requests
* @task uri Managing Diffusion URIs
*/
abstract class DiffusionRequest {
protected $callsign;
protected $path;
protected $line;
protected $branch;
protected $lint;
protected $symbolicCommit;
protected $symbolicType;
protected $stableCommit;
protected $repository;
protected $repositoryCommit;
protected $repositoryCommitData;
protected $arcanistProjects;
private $isClusterRequest = false;
private $initFromConduit = true;
private $user;
private $branchObject = false;
private $refAlternatives;
abstract public function supportsBranches();
abstract protected function isStableCommit($symbol);
protected function didInitialize() {
return null;
}
/* -( Creating Requests )-------------------------------------------------- */
/**
* Create a new synthetic request from a parameter dictionary. If you need
* a @{class:DiffusionRequest} object in order to issue a DiffusionQuery, you
* can use this method to build one.
*
* Parameters are:
*
* - `callsign` Repository callsign. Provide this or `repository`.
* - `user` Viewing user. Required if `callsign` is provided.
* - `repository` Repository object. Provide this or `callsign`.
* - `branch` Optional, branch name.
* - `path` Optional, file path.
* - `commit` Optional, commit identifier.
* - `line` Optional, line range.
*
* @param map See documentation.
* @return DiffusionRequest New request object.
* @task new
*/
final public static function newFromDictionary(array $data) {
if (isset($data['repository']) && isset($data['callsign'])) {
throw new Exception(
"Specify 'repository' or 'callsign', but not both.");
} else if (!isset($data['repository']) && !isset($data['callsign'])) {
throw new Exception(
"One of 'repository' and 'callsign' is required.");
} else if (isset($data['callsign']) && empty($data['user'])) {
throw new Exception(
"Parameter 'user' is required if 'callsign' is provided.");
}
if (isset($data['repository'])) {
$object = self::newFromRepository($data['repository']);
} else {
$object = self::newFromCallsign($data['callsign'], $data['user']);
}
$object->initializeFromDictionary($data);
return $object;
}
/**
* Create a new request from an Aphront request dictionary. This is an
* internal method that you generally should not call directly; instead,
* call @{method:newFromDictionary}.
*
* @param map Map of Aphront request data.
* @return DiffusionRequest New request object.
* @task new
*/
final public static function newFromAphrontRequestDictionary(
array $data,
AphrontRequest $request) {
$callsign = phutil_unescape_uri_path_component(idx($data, 'callsign'));
$object = self::newFromCallsign($callsign, $request->getUser());
$use_branches = $object->supportsBranches();
if (isset($data['dblob'])) {
$parsed = self::parseRequestBlob(idx($data, 'dblob'), $use_branches);
} else {
$parsed = array(
'commit' => idx($data, 'commit'),
'path' => idx($data, 'path'),
'line' => idx($data, 'line'),
'branch' => idx($data, 'branch'),
);
}
$object->setUser($request->getUser());
$object->initializeFromDictionary($parsed);
$object->lint = $request->getStr('lint');
return $object;
}
/**
* Internal.
*
* @task new
*/
final private function __construct() {
// <private>
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param string Repository callsign.
* @param PhabricatorUser Viewing user.
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromCallsign(
$callsign,
PhabricatorUser $viewer) {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withCallsigns(array($callsign))
->executeOne();
if (!$repository) {
throw new Exception("No such repository '{$callsign}'.");
}
return self::newFromRepository($repository);
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param PhabricatorRepository Repository object.
* @return DiffusionRequest New request object.
* @task new
*/
final private static function newFromRepository(
PhabricatorRepository $repository) {
$map = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'DiffusionGitRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => 'DiffusionSvnRequest',
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL =>
'DiffusionMercurialRequest',
);
$class = idx($map, $repository->getVersionControlSystem());
if (!$class) {
throw new Exception('Unknown version control system!');
}
$object = new $class();
$object->repository = $repository;
$object->callsign = $repository->getCallsign();
return $object;
}
/**
* Internal. Use @{method:newFromDictionary}, not this method.
*
* @param map Map of parsed data.
* @return void
* @task new
*/
final private function initializeFromDictionary(array $data) {
$this->path = idx($data, 'path');
$this->line = idx($data, 'line');
$this->initFromConduit = idx($data, 'initFromConduit', true);
$this->symbolicCommit = idx($data, 'commit');
if ($this->supportsBranches()) {
$this->branch = idx($data, 'branch');
}
if (!$this->getUser()) {
$user = idx($data, 'user');
if (!$user) {
throw new Exception(
'You must provide a PhabricatorUser in the dictionary!');
}
$this->setUser($user);
}
$this->didInitialize();
}
final public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
final public function getUser() {
return $this->user;
}
public function getRepository() {
return $this->repository;
}
public function getCallsign() {
return $this->callsign;
}
public function setPath($path) {
$this->path = $path;
return $this;
}
public function getPath() {
return $this->path;
}
public function getLine() {
return $this->line;
}
public function getCommit() {
// TODO: Probably remove all of this.
if ($this->getSymbolicCommit() !== null) {
return $this->getSymbolicCommit();
}
return $this->getStableCommit();
}
/**
* Get the symbolic commit associated with this request.
*
* A symbolic commit may be a commit hash, an abbreviated commit hash, a
* branch name, a tag name, or an expression like "HEAD^^^". The symbolic
* commit may also be absent.
*
* This method always returns the symbol present in the original request,
* in unmodified form.
*
* See also @{method:getStableCommit}.
*
* @return string|null Symbolic commit, if one was present in the request.
*/
public function getSymbolicCommit() {
return $this->symbolicCommit;
}
/**
* Modify the request to move the symbolic commit elsewhere.
*
* @param string New symbolic commit.
* @return this
*/
public function updateSymbolicCommit($symbol) {
$this->symbolicCommit = $symbol;
$this->symbolicType = null;
$this->stableCommit = null;
return $this;
}
/**
* Get the ref type (`commit` or `tag`) of the location associated with this
* request.
*
* If a symbolic commit is present in the request, this method identifies
* the type of the symbol. Otherwise, it identifies the type of symbol of
* the location the request is implicitly associated with. This will probably
* always be `commit`.
*
* @return string Symbolic commit type (`commit` or `tag`).
*/
public function getSymbolicType() {
if ($this->symbolicType === null) {
// As a side effect, this resolves the symbolic type.
$this->getStableCommit();
}
return $this->symbolicType;
}
/**
* Retrieve the stable, permanent commit name identifying the repository
* location associated with this request.
*
* This returns a non-symbolic identifier for the current commit: in Git and
* Mercurial, a 40-character SHA1; in SVN, a revision number.
*
* See also @{method:getSymbolicCommit}.
*
* @return string Stable commit name, like a git hash or SVN revision. Not
* a symbolic commit reference.
*/
public function getStableCommit() {
if (!$this->stableCommit) {
if ($this->isStableCommit($this->symbolicCommit)) {
$this->stableCommit = $this->symbolicCommit;
$this->symbolicType = 'commit';
} else {
$this->queryStableCommit();
}
}
return $this->stableCommit;
}
public function getBranch() {
return $this->branch;
}
public function getLint() {
return $this->lint;
}
protected function getArcanistBranch() {
return $this->getBranch();
}
public function loadBranch() {
// TODO: Get rid of this and do real Queries on real objects.
if ($this->branchObject === false) {
$this->branchObject = PhabricatorRepositoryBranch::loadBranch(
$this->getRepository()->getID(),
$this->getArcanistBranch());
}
return $this->branchObject;
}
public function loadCoverage() {
// TODO: This should also die.
$branch = $this->loadBranch();
if (!$branch) {
return;
}
$path = $this->getPath();
$path_map = id(new DiffusionPathIDQuery(array($path)))->loadPathIDs();
$coverage_row = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE branchID = %d AND pathID = %d
ORDER BY commitID DESC LIMIT 1',
'repository_coverage',
$branch->getID(),
$path_map[$path]);
if (!$coverage_row) {
return null;
}
return idx($coverage_row, 'coverage');
}
public function loadCommit() {
if (empty($this->repositoryCommit)) {
$repository = $this->getRepository();
$commit = id(new DiffusionCommitQuery())
->setViewer($this->getUser())
->withRepository($repository)
->withIdentifiers(array($this->getStableCommit()))
->executeOne();
if ($commit) {
$commit->attachRepository($repository);
}
$this->repositoryCommit = $commit;
}
return $this->repositoryCommit;
}
public function loadArcanistProjects() {
if (empty($this->arcanistProjects)) {
$projects = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere(
'repositoryID = %d',
$this->getRepository()->getID());
$this->arcanistProjects = $projects;
}
return $this->arcanistProjects;
}
public function loadCommitData() {
if (empty($this->repositoryCommitData)) {
$commit = $this->loadCommit();
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
$data = new PhabricatorRepositoryCommitData();
$data->setCommitMessage(
'(This commit has not been fully parsed yet.)');
}
$this->repositoryCommitData = $data;
}
return $this->repositoryCommitData;
}
/* -( Managing Diffusion URIs )-------------------------------------------- */
/**
* Generate a Diffusion URI using this request to provide defaults. See
* @{method:generateDiffusionURI} for details. This method is the same, but
* preserves the request parameters if they are not overridden.
*
* @param map See @{method:generateDiffusionURI}.
* @return PhutilURI Generated URI.
* @task uri
*/
public function generateURI(array $params) {
if (empty($params['stable'])) {
$default_commit = $this->getSymbolicCommit();
} else {
$default_commit = $this->getStableCommit();
}
$defaults = array(
'callsign' => $this->getCallsign(),
'path' => $this->getPath(),
'branch' => $this->getBranch(),
'commit' => $default_commit,
'lint' => idx($params, 'lint', $this->getLint()),
);
foreach ($defaults as $key => $val) {
if (!isset($params[$key])) { // Overwrite NULL.
$params[$key] = $val;
}
}
return self::generateDiffusionURI($params);
}
/**
* Generate a Diffusion URI from a parameter map. Applies the correct encoding
* and formatting to the URI. Parameters are:
*
* - `action` One of `history`, `browse`, `change`, `lastmodified`,
* `branch`, `tags`, `branches`, or `revision-ref`. The action specified
* by the URI.
* - `callsign` Repository callsign.
* - `branch` Optional if action is not `branch`, branch name.
* - `path` Optional, path to file.
* - `commit` Optional, commit identifier.
* - `line` Optional, line range.
* - `lint` Optional, lint code.
* - `params` Optional, query parameters.
*
* The function generates the specified URI and returns it.
*
* @param map See documentation.
* @return PhutilURI Generated URI.
* @task uri
*/
public static function generateDiffusionURI(array $params) {
$action = idx($params, 'action');
$callsign = idx($params, 'callsign');
$path = idx($params, 'path');
$branch = idx($params, 'branch');
$commit = idx($params, 'commit');
$line = idx($params, 'line');
if (strlen($callsign)) {
$callsign = phutil_escape_uri_path_component($callsign).'/';
}
if (strlen($branch)) {
$branch = phutil_escape_uri_path_component($branch).'/';
}
if (strlen($path)) {
$path = ltrim($path, '/');
$path = str_replace(array(';', '$'), array(';;', '$$'), $path);
$path = phutil_escape_uri($path);
}
$path = "{$branch}{$path}";
if (strlen($commit)) {
$commit = str_replace('$', '$$', $commit);
$commit = ';'.phutil_escape_uri($commit);
}
if (strlen($line)) {
$line = '$'.phutil_escape_uri($line);
}
$req_callsign = false;
$req_branch = false;
$req_commit = false;
switch ($action) {
case 'history':
case 'browse':
case 'change':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
case 'refs':
$req_callsign = true;
break;
case 'branch':
$req_callsign = true;
$req_branch = true;
break;
case 'commit':
$req_callsign = true;
$req_commit = true;
break;
}
if ($req_callsign && !strlen($callsign)) {
throw new Exception(
"Diffusion URI action '{$action}' requires callsign!");
}
if ($req_commit && !strlen($commit)) {
throw new Exception(
"Diffusion URI action '{$action}' requires commit!");
}
switch ($action) {
case 'change':
case 'history':
case 'browse':
case 'lastmodified':
case 'tags':
case 'branches':
case 'lint':
case 'pathtree':
case 'refs':
$uri = "/diffusion/{$callsign}{$action}/{$path}{$commit}{$line}";
break;
case 'branch':
if (strlen($path)) {
$uri = "/diffusion/{$callsign}repository/{$path}";
} else {
$uri = "/diffusion/{$callsign}";
}
break;
case 'external':
$commit = ltrim($commit, ';');
$uri = "/diffusion/external/{$commit}/";
break;
case 'rendering-ref':
// This isn't a real URI per se, it's passed as a query parameter to
// the ajax changeset stuff but then we parse it back out as though
// it came from a URI.
$uri = rawurldecode("{$path}{$commit}");
break;
case 'commit':
$commit = ltrim($commit, ';');
$callsign = rtrim($callsign, '/');
$uri = "/r{$callsign}{$commit}";
break;
default:
throw new Exception("Unknown Diffusion URI action '{$action}'!");
}
if ($action == 'rendering-ref') {
return $uri;
}
$uri = new PhutilURI($uri);
if (isset($params['lint'])) {
$params['params'] = idx($params, 'params', array()) + array(
'lint' => $params['lint'],
);
}
if (idx($params, 'params')) {
$uri->setQueryParams($params['params']);
}
return $uri;
}
/**
* Internal. Public only for unit tests.
*
* Parse the request URI into components.
*
* @param string URI blob.
* @param bool True if this VCS supports branches.
* @return map Parsed URI.
*
* @task uri
*/
public static function parseRequestBlob($blob, $supports_branches) {
$result = array(
'branch' => null,
'path' => null,
'commit' => null,
'line' => null,
);
$matches = null;
if ($supports_branches) {
// Consume the front part of the URI, up to the first "/". This is the
// path-component encoded branch name.
if (preg_match('@^([^/]+)/@', $blob, $matches)) {
$result['branch'] = phutil_unescape_uri_path_component($matches[1]);
$blob = substr($blob, strlen($matches[1]) + 1);
}
}
// Consume the back part of the URI, up to the first "$". Use a negative
// lookbehind to prevent matching '$$'. We double the '$' symbol when
// encoding so that files with names like "money/$100" will survive.
$pattern = '@(?:(?:^|[^$])(?:[$][$])*)[$]([\d-,]+)$@';
if (preg_match($pattern, $blob, $matches)) {
$result['line'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
// We've consumed the line number if it exists, so unescape "$" in the
// rest of the string.
$blob = str_replace('$$', '$', $blob);
// Consume the commit name, stopping on ';;'. We allow any character to
// appear in commits names, as they can sometimes be symbolic names (like
// tag names or refs).
if (preg_match('@(?:(?:^|[^;])(?:;;)*);([^;].*)$@', $blob, $matches)) {
$result['commit'] = $matches[1];
$blob = substr($blob, 0, -(strlen($matches[1]) + 1));
}
// We've consumed the commit if it exists, so unescape ";" in the rest
// of the string.
$blob = str_replace(';;', ';', $blob);
if (strlen($blob)) {
$result['path'] = $blob;
}
$parts = explode('/', $result['path']);
foreach ($parts as $part) {
// Prevent any hyjinx since we're ultimately shipping this to the
// filesystem under a lot of workflows.
if ($part == '..') {
throw new Exception('Invalid path URI.');
}
}
return $result;
}
/**
* Check that the working copy of the repository is present and readable.
*
* @param string Path to the working copy.
*/
protected function validateWorkingCopy($path) {
if (!is_readable(dirname($path))) {
$this->raisePermissionException();
}
if (!Filesystem::pathExists($path)) {
$this->raiseCloneException();
}
}
protected function raisePermissionException() {
$host = php_uname('n');
$callsign = $this->getRepository()->getCallsign();
throw new DiffusionSetupException(
"The clone of this repository ('{$callsign}') on the local machine ".
"('{$host}') could not be read. Ensure that the repository is in a ".
"location where the web server has read permissions.");
}
protected function raiseCloneException() {
$host = php_uname('n');
$callsign = $this->getRepository()->getCallsign();
throw new DiffusionSetupException(
"The working copy for this repository ('{$callsign}') hasn't been ".
"cloned yet on this machine ('{$host}'). Make sure you've started the ".
"Phabricator daemons. If this problem persists for longer than a clone ".
"should take, check the daemon logs (in the Daemon Console) to see if ".
"there were errors cloning the repository. Consult the 'Diffusion User ".
"Guide' in the documentation for help setting up repositories.");
}
private function queryStableCommit() {
$types = array();
if ($this->symbolicCommit) {
$ref = $this->symbolicCommit;
} else {
if ($this->supportsBranches()) {
- $ref = $this->getResolvableBranchName($this->getBranch());
+ $ref = $this->getBranch();
$types = array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
);
} else {
$ref = 'HEAD';
}
}
$results = $this->resolveRefs(array($ref), $types);
$matches = idx($results, $ref, array());
if (!$matches) {
$message = pht(
'Ref "%s" does not exist in this repository.',
$ref);
throw id(new DiffusionRefNotFoundException($message))
->setRef($ref);
}
if (count($matches) > 1) {
$match = $this->chooseBestRefMatch($ref, $matches);
} else {
$match = head($matches);
}
$this->stableCommit = $match['identifier'];
$this->symbolicType = $match['type'];
}
public function getRefAlternatives() {
// Make sure we've resolved the reference into a stable commit first.
try {
$this->getStableCommit();
} catch (DiffusionRefNotFoundException $ex) {
// If we have a bad reference, just return the empty set of
// alternatives.
}
return $this->refAlternatives;
}
private function chooseBestRefMatch($ref, array $results) {
// First, filter out less-desirable matches.
$candidates = array();
foreach ($results as $result) {
// Exclude closed heads.
if ($result['type'] == 'branch') {
if (idx($result, 'closed')) {
continue;
}
}
$candidates[] = $result;
}
// If we filtered everything, undo the filtering.
if (!$candidates) {
$candidates = $results;
}
// TODO: Do a better job of selecting the best match?
$match = head($candidates);
// After choosing the best alternative, save all the alternatives so the
// UI can show them to the user.
if (count($candidates) > 1) {
$this->refAlternatives = $candidates;
}
return $match;
}
- protected function getResolvableBranchName($branch) {
- return $branch;
- }
-
private function resolveRefs(array $refs, array $types) {
// First, try to resolve refs from fast cache sources.
$cached_query = id(new DiffusionCachedResolveRefsQuery())
->setRepository($this->getRepository())
->withRefs($refs);
if ($types) {
$cached_query->withTypes($types);
}
$cached_results = $cached_query->execute();
// Throw away all the refs we resolved. Hopefully, we'll throw away
// everything here.
foreach ($refs as $key => $ref) {
if (isset($cached_results[$ref])) {
unset($refs[$key]);
}
}
// If we couldn't pull everything out of the cache, execute the underlying
// VCS operation.
if ($refs) {
$vcs_results = DiffusionQuery::callConduitWithDiffusionRequest(
$this->getUser(),
$this,
'diffusion.resolverefs',
array(
'types' => $types,
'refs' => $refs,
));
} else {
$vcs_results = array();
}
return $vcs_results + $cached_results;
}
public function setIsClusterRequest($is_cluster_request) {
$this->isClusterRequest = $is_cluster_request;
return $this;
}
public function getIsClusterRequest() {
return $this->isClusterRequest;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 3, 5:26 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
166105
Default Alt Text
(39 KB)

Event Timeline