Differential D10600 Diff 27430 src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php
<?php | <?php | ||||
/** | /** | ||||
* NOTE: this class should only be used where local access to the repository | * NOTE: this class should only be used where local access to the repository | ||||
* is guaranteed and NOT from within the Diffusion application. Diffusion | * is guaranteed and NOT from within the Diffusion application. Diffusion | ||||
* should use Conduit method 'diffusion.filecontentquery' to get this sort | * should use Conduit method 'diffusion.filecontentquery' to get this sort | ||||
* of data. | * of data. | ||||
*/ | */ | ||||
abstract class DiffusionFileContentQuery extends DiffusionQuery { | abstract class DiffusionFileContentQuery extends DiffusionQuery { | ||||
private $needsBlame; | private $needsBlame; | ||||
private $fileContent; | private $fileContent; | ||||
private $viewer; | private $viewer; | ||||
private $cachedBlameInfo; | |||||
final public static function newFromDiffusionRequest( | final public static function newFromDiffusionRequest( | ||||
DiffusionRequest $request) { | DiffusionRequest $request) { | ||||
return parent::newQueryObject(__CLASS__, $request); | return parent::newQueryObject(__CLASS__, $request); | ||||
} | } | ||||
abstract public function getFileContentFuture(); | abstract public function getFileContentFuture(); | ||||
abstract protected function executeQueryFromFuture(Future $future); | abstract protected function executeQueryFromFuture(Future $future); | ||||
Show All 12 Lines | final public function loadFileContentFromFuture(Future $future) { | ||||
return $this->fileContent; | return $this->fileContent; | ||||
} | } | ||||
final protected function executeQuery() { | final protected function executeQuery() { | ||||
return $this->loadFileContentFromFuture($this->getFileContentFuture()); | return $this->loadFileContentFromFuture($this->getFileContentFuture()); | ||||
} | } | ||||
final public function loadFileContent() { | final public function loadFileContent() { | ||||
return $this->executeQuery(); | if ($this->needsBlame) { | ||||
$this->loadBlameCache(); | |||||
} | |||||
$content = $this->executeQuery(); | |||||
return $content; | |||||
} | } | ||||
final public function getRawData() { | final public function getRawData() { | ||||
return $this->fileContent->getCorpus(); | return $this->fileContent->getCorpus(); | ||||
} | } | ||||
/** Blame Cache Functions */ | |||||
// Returns array(pathid, commitId) for the queried file | |||||
private function getBlameCacheKey() { | |||||
$path_id = id( | |||||
new DiffusionPathIDQuery( | |||||
array($this->getRequest()->getPath()) | |||||
))->loadPathIDs(); | |||||
$path_id = array_shift($path_id); | |||||
// FIXME: The code in the diffusion.historyquery function should | |||||
// probably be moved to a separate Diffusion Query Class | |||||
// instead of sitting in the API directly | |||||
$latest_commit = DiffusionQuery::callConduitWithDiffusionRequest( | |||||
$this->getRequest()->getUser(), | |||||
$this->getRequest(), | |||||
'diffusion.historyquery', | |||||
array( | |||||
'commit' => $this->getRequest()->getCommit(), | |||||
'path' => $this->getRequest()->getPath(), | |||||
'callsign' => $this->getRequest()->getRepository()->getCallsign(), | |||||
'offset' => 0, | |||||
'limit' => 1, | |||||
)); | |||||
if (!isset($latest_commit['pathChanges']) || | |||||
!isset($latest_commit['pathChanges'][0])) { | |||||
throw new Exception('Unable to find the latest commit for path'); | |||||
} | |||||
$commit_id = $latest_commit['pathChanges'][0]['commitData']['commitID']; | |||||
return array($path_id, $commit_id); | |||||
} | |||||
private function loadBlameCache() { | |||||
try { | |||||
$key = $this->getBlameCacheKey(); | |||||
$cache_data = | |||||
PhabricatorRepositoryBlameCache::loadFromCache($key[0], $key[1]); | |||||
if ($cache_data) { | |||||
$this->cachedBlameInfo = $cache_data->getBlameDict(); | |||||
} | |||||
} catch (Exception $e) { | |||||
// Move on if the cache fails... | |||||
phlog($e); | |||||
} | |||||
} | |||||
private function writeBlameCache($blame_dict) { | |||||
$key = $this->getBlameCacheKey(); | |||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | |||||
PhabricatorRepositoryBlameCache::saveToCache($key[0], $key[1], $blame_dict); | |||||
unset($unguarded); | |||||
return true; | |||||
} | |||||
/** | /** | ||||
* Pretty hairy function. If getNeedsBlame is false, this returns | * Pretty hairy function. If getNeedsBlame is false, this returns | ||||
* | * | ||||
* ($text_list, array(), array()) | * ($text_list, array(), array()) | ||||
* | * | ||||
* Where $text_list is the raw file content with trailing new lines stripped. | * Where $text_list is the raw file content with trailing new lines stripped. | ||||
* | * | ||||
* If getNeedsBlame is true, this returns | * If getNeedsBlame is true, this returns | ||||
Show All 10 Lines | abstract class DiffusionFileContentQuery extends DiffusionQuery { | ||||
*/ | */ | ||||
final public function getBlameData() { | final public function getBlameData() { | ||||
$raw_data = preg_replace('/\n$/', '', $this->getRawData()); | $raw_data = preg_replace('/\n$/', '', $this->getRawData()); | ||||
$text_list = array(); | $text_list = array(); | ||||
$line_rev_dict = array(); | $line_rev_dict = array(); | ||||
$blame_dict = array(); | $blame_dict = array(); | ||||
if (!$this->getNeedsBlame()) { | if (!$this->getNeedsBlame(true)) { | ||||
$text_list = explode("\n", $raw_data); | $text_list = explode("\n", $raw_data); | ||||
} else if ($raw_data != '') { | } else if ($raw_data != '') { | ||||
// Check if we have a cached blame | |||||
if ($this->cachedBlameInfo) { | |||||
$text_list = explode("\n", $raw_data); | |||||
$cache_data = $this->cachedBlameInfo; | |||||
$commits = id(new DiffusionCommitQuery()) | |||||
->setViewer($this->getViewer()) | |||||
->withDefaultRepository($this->getRequest()->getRepository()) | |||||
->withIDs(array_unique($cache_data)) | |||||
->needCommitData(true) | |||||
->execute(); | |||||
// Rebuild line_rev_dict from cache | |||||
foreach ($cache_data as $k => $commit_id) { | |||||
$line_rev_dict[$k] = $commits[$commit_id]->getCommitIdentifier(); | |||||
} | |||||
} else { | |||||
$lines = array(); | $lines = array(); | ||||
foreach (explode("\n", $raw_data) as $k => $line) { | foreach (explode("\n", $raw_data) as $k => $line) { | ||||
$lines[$k] = $this->tokenizeLine($line); | $lines[$k] = $this->tokenizeLine($line); | ||||
list($rev_id, $author, $text) = $lines[$k]; | list($rev_id, $author, $text) = $lines[$k]; | ||||
$text_list[$k] = $text; | $text_list[$k] = $text; | ||||
$line_rev_dict[$k] = $rev_id; | $line_rev_dict[$k] = $rev_id; | ||||
} | } | ||||
$line_rev_dict = $this->processRevList($line_rev_dict); | $line_rev_dict = $this->processRevList($line_rev_dict); | ||||
foreach ($lines as $k => $line) { | |||||
list($rev_id, $author, $text) = $line; | |||||
$rev_id = $line_rev_dict[$k]; | |||||
if (!isset($blame_dict[$rev_id])) { | |||||
$blame_dict[$rev_id]['author'] = $author; | |||||
} | |||||
} | |||||
$repository = $this->getRequest()->getRepository(); | |||||
$commits = id(new DiffusionCommitQuery()) | $commits = id(new DiffusionCommitQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($this->getViewer()) | ||||
->withDefaultRepository($repository) | ->withDefaultRepository($this->getRequest()->getRepository()) | ||||
->withIdentifiers(array_unique($line_rev_dict)) | ->withIdentifiers(array_unique($line_rev_dict)) | ||||
->needCommitData(true) | |||||
->execute(); | ->execute(); | ||||
// Write results to cache | |||||
$cache_data = array(); | |||||
$commit_identifiers = mpull($commits, 'getID', 'getCommitIdentifier'); | |||||
foreach ($line_rev_dict as $k => $line) { | |||||
$cache_data[$k] = $commit_identifiers[$line]; | |||||
} | |||||
$this->writeBlameCache($cache_data); | |||||
} | |||||
foreach ($commits as $commit) { | foreach ($commits as $commit) { | ||||
$blame_dict[$commit->getCommitIdentifier()]['epoch'] = | $blame_dict[$commit->getCommitIdentifier()]['epoch'] = | ||||
$commit->getEpoch(); | $commit->getEpoch(); | ||||
} | |||||
if ($commits) { | $blame_dict[$commit->getCommitIdentifier()]['commitPHID'] = | ||||
$commits_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( | $commit->getPHID(); | ||||
'commitID IN (%Ls)', | |||||
mpull($commits, 'getID')); | $author_phid = $commit->getCommitData()->getCommitDetail('authorPHID'); | ||||
if ($author_phid) { | |||||
foreach ($commits_data as $data) { | $blame_dict[$commit->getCommitIdentifier()]['author'] = $author_phid; | ||||
$author_phid = $data->getCommitDetail('authorPHID'); | } else { | ||||
if (!$author_phid) { | $blame_dict[$commit->getCommitIdentifier()]['author'] = | ||||
continue; | $commit->getCommitData()->getCommitDetail('authorName'); | ||||
} | |||||
$commit = $commits[$data->getCommitID()]; | |||||
$commit_identifier = $commit->getCommitIdentifier(); | |||||
$blame_dict[$commit_identifier]['authorPHID'] = $author_phid; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return array($text_list, $line_rev_dict, $blame_dict); | return array($text_list, $line_rev_dict, $blame_dict); | ||||
} | } | ||||
abstract protected function tokenizeLine($line); | abstract protected function tokenizeLine($line); | ||||
public function setNeedsBlame($needs_blame) { | public function setNeedsBlame($needs_blame) { | ||||
$this->needsBlame = $needs_blame; | $this->needsBlame = $needs_blame; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getNeedsBlame() { | public function getNeedsBlame($ignore_cache = false) { | ||||
if ($ignore_cache) { | |||||
return $this->needsBlame; | return $this->needsBlame; | ||||
} else { | |||||
if (!$this->cachedBlameInfo) { | |||||
return $this->needsBlame; | |||||
} | |||||
} | |||||
return false; | |||||
} | } | ||||
public function setViewer(PhabricatorUser $user) { | public function setViewer(PhabricatorUser $user) { | ||||
$this->viewer = $user; | $this->viewer = $user; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getViewer() { | public function getViewer() { | ||||
return $this->viewer; | return $this->viewer; | ||||
} | } | ||||
protected function processRevList(array $rev_list) { | protected function processRevList(array $rev_list) { | ||||
return $rev_list; | return $rev_list; | ||||
} | } | ||||
} | } |