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:
parent
b5da7b3723
commit
fe78944c9d
11 changed files with 283 additions and 69 deletions
5
resources/sql/patches/20130226.commitkey.sql
Normal file
5
resources/sql/patches/20130226.commitkey.sql
Normal 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);
|
|
@ -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',
|
||||
|
|
|
@ -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]+)'
|
||||
|
|
140
src/applications/diffusion/query/DiffusionCommitQuery.php
Normal file
140
src/applications/diffusion/query/DiffusionCommitQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue