mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-26 06:28:19 +01:00
(stable) Promote 2018 Week 41
This commit is contained in:
commit
a0f323a72f
5 changed files with 303 additions and 60 deletions
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PhabricatorDifferentialApplication extends PhabricatorApplication {
|
final class PhabricatorDifferentialApplication
|
||||||
|
extends PhabricatorApplication {
|
||||||
|
|
||||||
public function getBaseURI() {
|
public function getBaseURI() {
|
||||||
return '/differential/';
|
return '/differential/';
|
||||||
|
@ -48,8 +49,7 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
|
||||||
'/(?P<filter>new)/' => 'DifferentialRevisionViewController',
|
'/(?P<filter>new)/' => 'DifferentialRevisionViewController',
|
||||||
),
|
),
|
||||||
'/differential/' => array(
|
'/differential/' => array(
|
||||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
$this->getQueryRoutePattern() => 'DifferentialRevisionListController',
|
||||||
=> 'DifferentialRevisionListController',
|
|
||||||
'diff/' => array(
|
'diff/' => array(
|
||||||
'(?P<id>[1-9]\d*)/' => array(
|
'(?P<id>[1-9]\d*)/' => array(
|
||||||
'' => 'DifferentialDiffViewController',
|
'' => 'DifferentialDiffViewController',
|
||||||
|
|
|
@ -446,7 +446,18 @@ final class DifferentialInlineCommentMailView
|
||||||
'style' => implode(' ', $link_style),
|
'style' => implode(' ', $link_style),
|
||||||
'href' => $link_href,
|
'href' => $link_href,
|
||||||
),
|
),
|
||||||
pht('View Inline'));
|
array(
|
||||||
|
pht('View Inline'),
|
||||||
|
|
||||||
|
// See PHI920. Add a space after the link so we render this into
|
||||||
|
// the document:
|
||||||
|
//
|
||||||
|
// View Inline filename.txt
|
||||||
|
//
|
||||||
|
// Otherwise, we render "Inlinefilename.txt" and double-clicking
|
||||||
|
// the file name selects the word "Inline" as well.
|
||||||
|
' ',
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
$link = null;
|
$link = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -289,4 +289,77 @@ final class DifferentialRevisionSearchEngine
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function newExportFields() {
|
||||||
|
$fields = array(
|
||||||
|
id(new PhabricatorStringExportField())
|
||||||
|
->setKey('monogram')
|
||||||
|
->setLabel(pht('Monogram')),
|
||||||
|
id(new PhabricatorPHIDExportField())
|
||||||
|
->setKey('authorPHID')
|
||||||
|
->setLabel(pht('Author PHID')),
|
||||||
|
id(new PhabricatorStringExportField())
|
||||||
|
->setKey('author')
|
||||||
|
->setLabel(pht('Author')),
|
||||||
|
id(new PhabricatorStringExportField())
|
||||||
|
->setKey('status')
|
||||||
|
->setLabel(pht('Status')),
|
||||||
|
id(new PhabricatorStringExportField())
|
||||||
|
->setKey('statusName')
|
||||||
|
->setLabel(pht('Status Name')),
|
||||||
|
id(new PhabricatorURIExportField())
|
||||||
|
->setKey('uri')
|
||||||
|
->setLabel(pht('URI')),
|
||||||
|
id(new PhabricatorStringExportField())
|
||||||
|
->setKey('title')
|
||||||
|
->setLabel(pht('Title')),
|
||||||
|
id(new PhabricatorStringExportField())
|
||||||
|
->setKey('summary')
|
||||||
|
->setLabel(pht('Summary')),
|
||||||
|
id(new PhabricatorStringExportField())
|
||||||
|
->setKey('testPlan')
|
||||||
|
->setLabel(pht('Test Plan')),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newExportData(array $revisions) {
|
||||||
|
$viewer = $this->requireViewer();
|
||||||
|
|
||||||
|
$phids = array();
|
||||||
|
foreach ($revisions as $revision) {
|
||||||
|
$phids[] = $revision->getAuthorPHID();
|
||||||
|
}
|
||||||
|
$handles = $viewer->loadHandles($phids);
|
||||||
|
|
||||||
|
$export = array();
|
||||||
|
foreach ($revisions as $revision) {
|
||||||
|
|
||||||
|
$author_phid = $revision->getAuthorPHID();
|
||||||
|
if ($author_phid) {
|
||||||
|
$author_name = $handles[$author_phid]->getName();
|
||||||
|
} else {
|
||||||
|
$author_name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = $revision->getStatusObject();
|
||||||
|
$status_name = $status->getDisplayName();
|
||||||
|
$status_value = $status->getKey();
|
||||||
|
|
||||||
|
$export[] = array(
|
||||||
|
'monogram' => $revision->getMonogram(),
|
||||||
|
'authorPHID' => $author_phid,
|
||||||
|
'author' => $author_name,
|
||||||
|
'status' => $status_value,
|
||||||
|
'statusName' => $status_name,
|
||||||
|
'uri' => PhabricatorEnv::getProductionURI($revision->getURI()),
|
||||||
|
'title' => (string)$revision->getTitle(),
|
||||||
|
'summary' => (string)$revision->getSummary(),
|
||||||
|
'testPlan' => (string)$revision->getTestPlan(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $export;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@ final class PhabricatorBulkManagementExportWorkflow
|
||||||
'name' => 'query',
|
'name' => 'query',
|
||||||
'param' => 'key',
|
'param' => 'key',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Export the data selected by this query.'),
|
'Export the data selected by one or more queries.'),
|
||||||
|
'repeat' => true,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'name' => 'output',
|
'name' => 'output',
|
||||||
|
@ -47,56 +48,7 @@ final class PhabricatorBulkManagementExportWorkflow
|
||||||
public function execute(PhutilArgumentParser $args) {
|
public function execute(PhutilArgumentParser $args) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$class = $args->getArg('class');
|
list($engine, $queries) = $this->newQueries($args);
|
||||||
|
|
||||||
if (!strlen($class)) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'Specify a search engine class to export data from with '.
|
|
||||||
'"--class".'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_subclass_of($class, 'PhabricatorApplicationSearchEngine')) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'SearchEngine class ("%s") is unknown.',
|
|
||||||
$class));
|
|
||||||
}
|
|
||||||
|
|
||||||
$engine = newv($class, array())
|
|
||||||
->setViewer($viewer);
|
|
||||||
|
|
||||||
if (!$engine->canExport()) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'SearchEngine class ("%s") does not support data export.',
|
|
||||||
$class));
|
|
||||||
}
|
|
||||||
|
|
||||||
$query_key = $args->getArg('query');
|
|
||||||
if (!strlen($query_key)) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'Specify a query to export with "--query".'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($engine->isBuiltinQuery($query_key)) {
|
|
||||||
$saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
|
|
||||||
} else if ($query_key) {
|
|
||||||
$saved_query = id(new PhabricatorSavedQueryQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withQueryKeys(array($query_key))
|
|
||||||
->executeOne();
|
|
||||||
} else {
|
|
||||||
$saved_query = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$saved_query) {
|
|
||||||
throw new PhutilArgumentUsageException(
|
|
||||||
pht(
|
|
||||||
'Failed to load saved query ("%s").',
|
|
||||||
$query_key));
|
|
||||||
}
|
|
||||||
|
|
||||||
$format_key = $args->getArg('format');
|
$format_key = $args->getArg('format');
|
||||||
if (!strlen($format_key)) {
|
if (!strlen($format_key)) {
|
||||||
|
@ -125,14 +77,27 @@ final class PhabricatorBulkManagementExportWorkflow
|
||||||
$is_overwrite = $args->getArg('overwrite');
|
$is_overwrite = $args->getArg('overwrite');
|
||||||
$output_path = $args->getArg('output');
|
$output_path = $args->getArg('output');
|
||||||
|
|
||||||
if (!strlen($output_path) && $is_overwrite) {
|
if (!strlen($output_path)) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht(
|
||||||
'Flag "--overwrite" has no effect without "--output".'));
|
'Use "--output <path>" to specify an output file, or "--output -" '.
|
||||||
|
'to print to stdout.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($output_path === '-') {
|
||||||
|
$is_stdout = true;
|
||||||
|
} else {
|
||||||
|
$is_stdout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_stdout && $is_overwrite) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Flag "--overwrite" has no effect when outputting to stdout.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$is_overwrite) {
|
if (!$is_overwrite) {
|
||||||
if (Filesystem::pathExists($output_path)) {
|
if (!$is_stdout && Filesystem::pathExists($output_path)) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht(
|
||||||
'Output path already exists. Use "--overwrite" to overwrite '.
|
'Output path already exists. Use "--overwrite" to overwrite '.
|
||||||
|
@ -140,6 +105,15 @@ final class PhabricatorBulkManagementExportWorkflow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have more than one query, execute the queries to figure out which
|
||||||
|
// results they hit, then build a synthetic query for all those results
|
||||||
|
// using the IDs.
|
||||||
|
if (count($queries) > 1) {
|
||||||
|
$saved_query = $this->newUnionQuery($engine, $queries);
|
||||||
|
} else {
|
||||||
|
$saved_query = head($queries);
|
||||||
|
}
|
||||||
|
|
||||||
$export_engine = id(new PhabricatorExportEngine())
|
$export_engine = id(new PhabricatorExportEngine())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setTitle(pht('Export'))
|
->setTitle(pht('Export'))
|
||||||
|
@ -152,10 +126,20 @@ final class PhabricatorBulkManagementExportWorkflow
|
||||||
|
|
||||||
$iterator = $file->getFileDataIterator();
|
$iterator = $file->getFileDataIterator();
|
||||||
|
|
||||||
if (strlen($output_path)) {
|
if (!$is_stdout) {
|
||||||
|
// Empty the file before we start writing to it. Otherwise, "--overwrite"
|
||||||
|
// will really mean "--append".
|
||||||
|
Filesystem::writeFile($output_path, '');
|
||||||
|
|
||||||
foreach ($iterator as $chunk) {
|
foreach ($iterator as $chunk) {
|
||||||
Filesystem::appendFile($output_path, $chunk);
|
Filesystem::appendFile($output_path, $chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo tsprintf(
|
||||||
|
"%s\n",
|
||||||
|
pht(
|
||||||
|
'Exported data to "%s".',
|
||||||
|
Filesystem::readablePath($output_path)));
|
||||||
} else {
|
} else {
|
||||||
foreach ($iterator as $chunk) {
|
foreach ($iterator as $chunk) {
|
||||||
echo $chunk;
|
echo $chunk;
|
||||||
|
@ -165,4 +149,179 @@ final class PhabricatorBulkManagementExportWorkflow
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function newQueries(PhutilArgumentParser $args) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$query_keys = $args->getArg('query');
|
||||||
|
if (!$query_keys) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Specify one or more queries to export with "--query".'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$engine_classes = id(new PhutilClassMapQuery())
|
||||||
|
->setAncestorClass('PhabricatorApplicationSearchEngine')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$class = $args->getArg('class');
|
||||||
|
if (strlen($class)) {
|
||||||
|
|
||||||
|
$class_list = array();
|
||||||
|
foreach ($engine_classes as $class_name => $engine_object) {
|
||||||
|
$can_export = id(clone $engine_object)
|
||||||
|
->setViewer($viewer)
|
||||||
|
->canExport();
|
||||||
|
if ($can_export) {
|
||||||
|
$class_list[] = $class_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort($class_list);
|
||||||
|
$class_list = implode(', ', $class_list);
|
||||||
|
|
||||||
|
$matches = array();
|
||||||
|
foreach ($engine_classes as $class_name => $engine_object) {
|
||||||
|
if (stripos($class_name, $class) !== false) {
|
||||||
|
if (strtolower($class_name) == strtolower($class)) {
|
||||||
|
$matches = array($class_name);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
$matches[] = $class_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$matches) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'No search engines match "%s". Available engines which support '.
|
||||||
|
'data export are: %s.',
|
||||||
|
$class,
|
||||||
|
$class_list));
|
||||||
|
} else if (count($matches) > 1) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Multiple search engines match "%s": %s.',
|
||||||
|
$class,
|
||||||
|
implode(', ', $matches)));
|
||||||
|
} else {
|
||||||
|
$class = head($matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
$engine = newv($class, array())
|
||||||
|
->setViewer($viewer);
|
||||||
|
} else {
|
||||||
|
$engine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$queries = array();
|
||||||
|
foreach ($query_keys as $query_key) {
|
||||||
|
if ($engine) {
|
||||||
|
if ($engine->isBuiltinQuery($query_key)) {
|
||||||
|
$queries[$query_key] = $engine->buildSavedQueryFromBuiltin(
|
||||||
|
$query_key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved_query = id(new PhabricatorSavedQueryQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withQueryKeys(array($query_key))
|
||||||
|
->executeOne();
|
||||||
|
if (!$saved_query) {
|
||||||
|
if (!$engine) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Query "%s" is unknown. To run a builtin query like "all" or '.
|
||||||
|
'"active", also specify the search engine with "--class".',
|
||||||
|
$query_key));
|
||||||
|
} else {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Query "%s" is not a recognized query for class "%s".',
|
||||||
|
$query_key,
|
||||||
|
get_class($engine)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$queries[$query_key] = $saved_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have an engine from "--class", fill it in by looking at the
|
||||||
|
// class of the first query.
|
||||||
|
if (!$engine) {
|
||||||
|
foreach ($queries as $query) {
|
||||||
|
$engine = newv($query->getEngineClassName(), array())
|
||||||
|
->setViewer($viewer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$engine_class = get_class($engine);
|
||||||
|
|
||||||
|
foreach ($queries as $query) {
|
||||||
|
$query_class = $query->getEngineClassName();
|
||||||
|
if ($query_class !== $engine_class) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Specified queries use different engines: query "%s" uses '.
|
||||||
|
'engine "%s", not "%s". All queries must run on the same '.
|
||||||
|
'engine.',
|
||||||
|
$query->getQueryKey(),
|
||||||
|
$query_class,
|
||||||
|
$engine_class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$engine->canExport()) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'SearchEngine class ("%s") does not support data export.',
|
||||||
|
$engine_class));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($engine, $queries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newUnionQuery(
|
||||||
|
PhabricatorApplicationSearchEngine $engine,
|
||||||
|
array $queries) {
|
||||||
|
|
||||||
|
assert_instances_of($queries, 'PhabricatorSavedQuery');
|
||||||
|
|
||||||
|
$engine = clone $engine;
|
||||||
|
|
||||||
|
$ids = array();
|
||||||
|
foreach ($queries as $saved_query) {
|
||||||
|
$page_size = 1000;
|
||||||
|
$page_cursor = null;
|
||||||
|
do {
|
||||||
|
$query = $engine->buildQueryFromSavedQuery($saved_query);
|
||||||
|
$pager = $engine->newPagerForSavedQuery($saved_query);
|
||||||
|
$pager->setPageSize($page_size);
|
||||||
|
|
||||||
|
if ($page_cursor !== null) {
|
||||||
|
$pager->setAfterID($page_cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
$objects = $engine->executeQuery($query, $pager);
|
||||||
|
$page_cursor = $pager->getNextPageID();
|
||||||
|
|
||||||
|
foreach ($objects as $object) {
|
||||||
|
$ids[] = $object->getID();
|
||||||
|
}
|
||||||
|
} while ($pager->getHasMoreResults());
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we're merging multiple different queries, override any query order
|
||||||
|
// and just put the combined result list in ID order. At time of writing,
|
||||||
|
// we can't merge the result sets together while retaining the overall sort
|
||||||
|
// order even if they all used the same order, and it's meaningless to try
|
||||||
|
// to retain orders if the queries had different orders in the first place.
|
||||||
|
rsort($ids);
|
||||||
|
|
||||||
|
return id($engine->newSavedQuery())
|
||||||
|
->setParameter('ids', $ids);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ final class PhabricatorExportEngine
|
||||||
$field_list = mpull($field_list, null, 'getKey');
|
$field_list = mpull($field_list, null, 'getKey');
|
||||||
$format->addHeaders($field_list);
|
$format->addHeaders($field_list);
|
||||||
|
|
||||||
// Iterate over the query results in large page so we don't have to hold
|
// Iterate over the query results in large pages so we don't have to hold
|
||||||
// too much stuff in memory.
|
// too much stuff in memory.
|
||||||
$page_size = 1000;
|
$page_size = 1000;
|
||||||
$page_cursor = null;
|
$page_cursor = null;
|
||||||
|
|
Loading…
Add table
Reference in a new issue