1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Prepare Diffusion for hovercards

Summary:
Move Diffusion to be hovercard-ready, and expand our ability to resolve commit references.

  - Link unqualified hashes of 7 characters or more which match a commit.
  - Link qualified hashes of 5 characters or more which match a commit.
  - Support `{...}` syntax.

Test Plan: {F33896}

Reviewers: chad, vrana

Reviewed By: vrana

CC: aran

Differential Revision: https://secure.phabricator.com/D5121
This commit is contained in:
epriestley 2013-02-27 08:04:54 -08:00
parent b5da7b3723
commit fe78944c9d
11 changed files with 283 additions and 69 deletions

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_repository.repository_commit
DROP KEY `repositoryID`;
ALTER TABLE {$NAMESPACE}_repository.repository_commit
ADD UNIQUE KEY `key_commit_identity` (commitIdentifier, repositoryID);

View file

@ -374,6 +374,7 @@ phutil_register_library_map(array(
'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php',
'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php',
'DiffusionCommitParentsQuery' => 'applications/diffusion/query/parents/DiffusionCommitParentsQuery.php',
'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php',
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
'DiffusionCommitTagsQuery' => 'applications/diffusion/query/committags/DiffusionCommitTagsQuery.php',
'DiffusionContainsQuery' => 'applications/diffusion/query/contains/DiffusionContainsQuery.php',
@ -1186,7 +1187,6 @@ phutil_register_library_map(array(
'PhabricatorRemarkupRuleMeme' => 'applications/macro/remarkup/PhabricatorRemarkupRuleMeme.php',
'PhabricatorRemarkupRuleMention' => 'applications/people/remarkup/PhabricatorRemarkupRuleMention.php',
'PhabricatorRemarkupRuleObject' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObject.php',
'PhabricatorRemarkupRuleObjectName' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleObjectName.php',
'PhabricatorRemarkupRuleYoutube' => 'infrastructure/markup/rule/PhabricatorRemarkupRuleYoutube.php',
'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php',
'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/PhabricatorRepositoryArcanistProject.php',
@ -1898,6 +1898,7 @@ phutil_register_library_map(array(
'DiffusionCommitController' => 'DiffusionController',
'DiffusionCommitEditController' => 'DiffusionController',
'DiffusionCommitParentsQuery' => 'DiffusionQuery',
'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DiffusionCommitTagsController' => 'DiffusionController',
'DiffusionCommitTagsQuery' => 'DiffusionQuery',
'DiffusionContainsQuery' => 'DiffusionQuery',
@ -1953,7 +1954,7 @@ phutil_register_library_map(array(
'DiffusionPathValidateController' => 'DiffusionController',
'DiffusionPeopleMenuEventListener' => 'PhutilEventListener',
'DiffusionRawDiffQuery' => 'DiffusionQuery',
'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObjectName',
'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'DiffusionRepositoryController' => 'DiffusionController',
'DiffusionSetupException' => 'AphrontUsageException',
'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery',
@ -2661,7 +2662,6 @@ phutil_register_library_map(array(
'PhabricatorRemarkupRuleMeme' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleMention' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleObject' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleObjectName' => 'PhutilRemarkupRule',
'PhabricatorRemarkupRuleYoutube' => 'PhutilRemarkupRule',
'PhabricatorRepository' =>
array(
@ -2673,7 +2673,11 @@ phutil_register_library_map(array(
'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController',
'PhabricatorRepositoryAuditRequest' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryBranch' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommit' =>
array(
0 => 'PhabricatorRepositoryDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker',

View file

@ -30,6 +30,12 @@ final class PhabricatorApplicationDiffusion extends PhabricatorApplication {
);
}
public function getRemarkupRules() {
return array(
new DiffusionRemarkupRule(),
);
}
public function getRoutes() {
return array(
'/r(?P<callsign>[A-Z]+)(?P<commit>[a-z0-9]+)'

View file

@ -0,0 +1,140 @@
<?php
final class DiffusionCommitQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $identifiers;
/**
* Load commits by partial or full identifiers, e.g. "rXab82393", "rX1234",
* or "a9caf12". When an identifier matches multiple commits, they will all
* be returned; callers should be prepared to deal with more results than
* they queried for.
*/
public function withIdentifiers(array $identifiers) {
$this->identifiers = $identifiers;
return $this;
}
public function loadPage() {
$table = new PhabricatorRepositoryCommit();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
public function willFilterPage(array $commits) {
if (!$commits) {
return array();
}
$repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID');
$repos = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withIDs($repository_ids)
->execute();
foreach ($commits as $key => $commit) {
$repo = idx($repos, $commit->getRepositoryID());
if ($repo) {
$commit->attachRepository($repo);
} else {
unset($commits[$key]);
}
}
return $commits;
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->identifiers) {
$min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH;
$min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
$refs = array();
$bare = array();
foreach ($this->identifiers as $identifier) {
$matches = null;
preg_match('/^(?:r([A-Z]+))?(.*)$/', $identifier, $matches);
$repo = nonempty($matches[1], null);
$identifier = nonempty($matches[2], null);
if ($repo === null) {
if (strlen($identifier) < $min_unqualified) {
continue;
}
$bare[] = $identifier;
} else {
$refs[] = array(
'callsign' => $repo,
'identifier' => $identifier,
);
}
}
$sql = array();
foreach ($bare as $identifier) {
$sql[] = qsprintf(
$conn_r,
'(commitIdentifier LIKE %> AND LENGTH(commitIdentifier) = 40)',
$identifier);
}
if ($refs) {
$callsigns = ipull($refs, 'callsign');
$repos = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withCallsigns($callsigns)
->execute();
$repos = mpull($repos, null, 'getCallsign');
foreach ($refs as $key => $ref) {
$repo = idx($repos, $ref['callsign']);
if (!$repo) {
continue;
}
if ($repo->isSVN()) {
$sql[] = qsprintf(
$conn_r,
'(repositoryID = %d AND commitIdentifier = %d)',
$repo->getID(),
$ref['identifier']);
} else {
if (strlen($ref['identifier']) < $min_qualified) {
continue;
}
$sql[] = qsprintf(
$conn_r,
'(repositoryID = %d AND commitIdentifier LIKE %>)',
$repo->getID(),
$ref['identifier']);
}
}
}
if ($sql) {
$where[] = '('.implode(' OR ', $sql).')';
} else {
// If we discarded all possible identifiers (e.g., they all referenced
// bogus repositories or were all too short), make sure the query finds
// nothing.
$where[] = qsprintf($conn_r, '1 = 0');
}
}
return $this->formatWhereClause($where);
}
}

View file

@ -1,17 +1,79 @@
<?php
/**
* @group markup
*/
final class DiffusionRemarkupRule
extends PhabricatorRemarkupRuleObjectName {
extends PhabricatorRemarkupRuleObject {
protected function getObjectNamePrefix() {
return 'r';
return '';
}
protected function getObjectIDPattern() {
return '[A-Z]+[a-f0-9]+';
$min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH;
$min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
return
'(?:r[A-Z]+)?[0-9]+'.
'|'.
'(?:r[A-Z]+)?[a-f0-9]{'.$min_qualified.',40}'.
'|'.
'[a-f0-9]{'.$min_unqualified.',40}';
}
protected function loadObjects(array $ids) {
$viewer = $this->getEngine()->getConfig('viewer');
$min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
if (!$viewer) {
return array();
}
$commits = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withIdentifiers($ids)
->execute();
if (!$commits) {
return array();
}
$ids = array_fuse($ids);
$result = array();
foreach ($commits as $commit) {
$prefix = 'r'.$commit->getRepository()->getCallsign();
$suffix = $commit->getCommitIdentifier();
if ($commit->getRepository()->isSVN()) {
if (isset($ids[$prefix.$suffix])) {
$result[$prefix.$suffix][] = $commit;
}
} else {
// This awkward contruction is so we can link the commits up in O(N)
// time instead of O(N^2).
for ($ii = $min_qualified; $ii <= strlen($suffix); $ii++) {
$part = substr($suffix, 0, $ii);
if (isset($ids[$prefix.$part])) {
$result[$prefix.$part][] = $commit;
}
if (isset($ids[$part])) {
$result[$part][] = $commit;
}
}
}
}
foreach ($result as $identifier => $commits) {
if (count($commits) == 1) {
$result[$identifier] = head($commits);
} else {
// This reference is ambiguous -- it matches more than one commit -- so
// don't link it. We could potentially improve this, but it's a bit
// tricky since the superclass expects a single object.
unset($result[$identifier]);
}
}
return $result;
}
}

View file

@ -6,6 +6,16 @@
final class PhabricatorRepository extends PhabricatorRepositoryDAO
implements PhabricatorPolicyInterface {
/**
* Shortest hash we'll recognize in raw "a829f32" form.
*/
const MINIMUM_UNQUALIFIED_HASH = 7;
/**
* Shortest hash we'll recognize in qualified "rXab7ef2f8" form.
*/
const MINIMUM_QUALIFIED_HASH = 5;
const TABLE_PATH = 'repository_path';
const TABLE_PATHCHANGE = 'repository_pathchange';
const TABLE_FILESYSTEM = 'repository_filesystem';

View file

@ -1,6 +1,8 @@
<?php
final class PhabricatorRepositoryCommit extends PhabricatorRepositoryDAO {
final class PhabricatorRepositoryCommit
extends PhabricatorRepositoryDAO
implements PhabricatorPolicyInterface {
protected $repositoryID;
protected $phid;
@ -14,6 +16,19 @@ final class PhabricatorRepositoryCommit extends PhabricatorRepositoryDAO {
private $commitData;
private $audits;
private $isUnparsed;
private $repository;
public function attachRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getRepository() {
if ($this->repository === null) {
throw new Exception("Call attachRepository() before getRepository()!");
}
return $this->repository;
}
public function setIsUnparsed($is_unparsed) {
$this->isUnparsed = $is_unparsed;
@ -138,4 +153,22 @@ final class PhabricatorRepositoryCommit extends PhabricatorRepositoryDAO {
return $this->setAuditStatus($status);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->getRepository()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
}
}

View file

@ -41,7 +41,7 @@ final class PhabricatorMarkupEngine {
private $objects = array();
private $viewer;
private $version = 4;
private $version = 5;
/* -( Markup Pipeline )---------------------------------------------------- */
@ -401,9 +401,6 @@ final class PhabricatorMarkupEngine {
$rules[] = new PhrictionRemarkupRule();
$rules[] = new PhabricatorRemarkupRuleEmbedFile();
$rules[] = new DiffusionRemarkupRule();
$rules[] = new PhabricatorCountdownRemarkupRule();
$applications = PhabricatorApplication::getAllInstalledApplications();

View file

@ -36,9 +36,9 @@ abstract class PhabricatorRemarkupRuleObject
return $result;
}
protected function renderObjectRef($object, $handle, $anchor) {
protected function renderObjectRef($object, $handle, $anchor, $id) {
$href = $handle->getURI();
$text = $this->getObjectNamePrefix().$object->getID();
$text = $this->getObjectNamePrefix().$id;
if ($anchor) {
$matches = null;
if (preg_match('@^#(?:comment-)?(\d{1,7})$@', $anchor, $matches)) {
@ -172,7 +172,11 @@ abstract class PhabricatorRemarkupRuleObject
$object = $objects[$spec['id']];
switch ($spec['type']) {
case 'ref':
$view = $this->renderObjectRef($object, $handle, $spec['anchor']);
$view = $this->renderObjectRef(
$object,
$handle,
$spec['anchor'],
$spec['id']);
break;
case 'embed':
$view = $this->renderObjectEmbed($object, $handle, $spec['options']);

View file

@ -1,51 +0,0 @@
<?php
/**
* @group markup
*/
abstract class PhabricatorRemarkupRuleObjectName
extends PhutilRemarkupRule {
abstract protected function getObjectNamePrefix();
protected function getObjectIDPattern() {
return '[1-9]\d*';
}
public function apply($text) {
$prefix = $this->getObjectNamePrefix();
$id = $this->getObjectIDPattern();
return preg_replace_callback(
"@\b({$prefix})({$id})(?:#([-\w\d]+))?\b@",
array($this, 'markupObjectNameLink'),
$text);
}
public function markupObjectNameLink($matches) {
list(, $prefix, $id) = $matches;
if (isset($matches[3])) {
$href = $matches[3];
$text = $matches[3];
if (preg_match('@^(?:comment-)?(\d{1,7})$@', $href, $matches)) {
// Maximum length is 7 because 12345678 could be a file hash.
$href = "comment-{$matches[1]}";
$text = $matches[1];
}
$href = "/{$prefix}{$id}#{$href}";
$text = "{$prefix}{$id}#{$text}";
} else {
$href = "/{$prefix}{$id}";
$text = "{$prefix}{$id}";
}
return $this->getEngine()->storeText(
phutil_tag(
'a',
array(
'href' => $href,
),
$text));
}
}

View file

@ -1153,6 +1153,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql',
'name' => $this->getPatchPath('20130222.dropchannel.sql'),
),
'20130226.commitkey.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130226.commitkey.sql'),
),
);
}