Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/query/blame/DiffusionBlameQuery.php
Show All 28 Lines | abstract class DiffusionBlameQuery extends DiffusionQuery { | ||||
final public static function newFromDiffusionRequest( | final public static function newFromDiffusionRequest( | ||||
DiffusionRequest $request) { | DiffusionRequest $request) { | ||||
return parent::newQueryObject(__CLASS__, $request); | return parent::newQueryObject(__CLASS__, $request); | ||||
} | } | ||||
final protected function executeQuery() { | final protected function executeQuery() { | ||||
$paths = $this->getPaths(); | $paths = $this->getPaths(); | ||||
$blame = array(); | |||||
// Load cache keys: these are the commits at which each path was last | |||||
// touched. | |||||
$keys = $this->loadCacheKeys($paths); | |||||
// Try to read blame data from cache. | |||||
$cache = $this->readCacheData($keys); | |||||
foreach ($paths as $key => $path) { | |||||
if (!isset($cache[$path])) { | |||||
continue; | |||||
} | |||||
$blame[$path] = $cache[$path]; | |||||
unset($paths[$key]); | |||||
} | |||||
// If we have no paths left, we filled everything from cache and can | |||||
// bail out early. | |||||
if (!$paths) { | |||||
return $blame; | |||||
} | |||||
$request = $this->getRequest(); | $request = $this->getRequest(); | ||||
$timeout = $this->getTimeout(); | $timeout = $this->getTimeout(); | ||||
// We're still missing at least some data, so we need to run VCS commands | |||||
// to pull it. | |||||
$futures = array(); | $futures = array(); | ||||
foreach ($paths as $path) { | foreach ($paths as $path) { | ||||
$future = $this->newBlameFuture($request, $path); | $future = $this->newBlameFuture($request, $path); | ||||
if ($timeout) { | if ($timeout) { | ||||
$future->setTimeout($timeout); | $future->setTimeout($timeout); | ||||
} | } | ||||
$futures[$path] = $future; | $futures[$path] = $future; | ||||
} | } | ||||
$blame = array(); | |||||
if ($futures) { | |||||
$futures = id(new FutureIterator($futures)) | $futures = id(new FutureIterator($futures)) | ||||
->limit(4); | ->limit(4); | ||||
foreach ($futures as $path => $future) { | foreach ($futures as $path => $future) { | ||||
$path_blame = $this->resolveBlameFuture($future); | $path_blame = $this->resolveBlameFuture($future); | ||||
if ($path_blame !== null) { | if ($path_blame !== null) { | ||||
$blame[$path] = $path_blame; | $blame[$path] = $path_blame; | ||||
} | } | ||||
} | } | ||||
} | |||||
// Fill the cache with anything we generated. | |||||
$this->writeCacheData( | |||||
array_select_keys($keys, $paths), | |||||
$blame); | |||||
return $blame; | return $blame; | ||||
} | } | ||||
private function loadCacheKeys(array $paths) { | |||||
$request = $this->getRequest(); | |||||
$viewer = $request->getUser(); | |||||
$repository = $request->getRepository(); | |||||
$repository_id = $repository->getID(); | |||||
$last_modified = parent::callConduitWithDiffusionRequest( | |||||
$viewer, | |||||
$request, | |||||
'diffusion.lastmodifiedquery', | |||||
array( | |||||
'paths' => array_fill_keys($paths, $request->getCommit()), | |||||
)); | |||||
$map = array(); | |||||
foreach ($paths as $path) { | |||||
$identifier = idx($last_modified, $path); | |||||
if ($identifier === null) { | |||||
continue; | |||||
} | |||||
$map[$path] = "blame({$repository_id}, {$identifier}, {$path}, raw)"; | |||||
} | |||||
return $map; | |||||
} | |||||
private function readCacheData(array $keys) { | |||||
$cache = PhabricatorCaches::getImmutableCache(); | |||||
$data = $cache->getKeys($keys); | |||||
$results = array(); | |||||
foreach ($keys as $path => $key) { | |||||
if (!isset($data[$key])) { | |||||
continue; | |||||
} | |||||
$results[$path] = $data[$key]; | |||||
} | |||||
// Decode the cache storage format. | |||||
foreach ($results as $path => $cache) { | |||||
list($head, $body) = explode("\n", $cache, 2); | |||||
switch ($head) { | |||||
case 'raw': | |||||
$body = explode("\n", $body); | |||||
break; | |||||
default: | |||||
$body = null; | |||||
break; | |||||
} | |||||
if ($body === null) { | |||||
unset($results[$path]); | |||||
} else { | |||||
$results[$path] = $body; | |||||
} | |||||
} | |||||
return $results; | |||||
} | |||||
private function writeCacheData(array $keys, array $blame) { | |||||
$writes = array(); | |||||
foreach ($keys as $path => $key) { | |||||
$value = idx($blame, $path); | |||||
if ($value === null) { | |||||
continue; | |||||
} | |||||
// For now, just store the entire value with a "raw" header. In the | |||||
// future, we could compress this or use IDs instead. | |||||
$value = "raw\n".implode("\n", $value); | |||||
$writes[$key] = $value; | |||||
} | |||||
if (!$writes) { | |||||
return; | |||||
} | |||||
$cache = PhabricatorCaches::getImmutableCache(); | |||||
$data = $cache->setKeys($writes, phutil_units('14 days in seconds')); | |||||
} | |||||
} | } |