1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-25 05:58:21 +01:00

(stable) Promote 2018 Week 41

This commit is contained in:
epriestley 2018-10-16 23:43:53 -07:00
commit a0f323a72f
5 changed files with 303 additions and 60 deletions

View file

@ -1,6 +1,7 @@
<?php
final class PhabricatorDifferentialApplication extends PhabricatorApplication {
final class PhabricatorDifferentialApplication
extends PhabricatorApplication {
public function getBaseURI() {
return '/differential/';
@ -48,8 +49,7 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
'/(?P<filter>new)/' => 'DifferentialRevisionViewController',
),
'/differential/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'DifferentialRevisionListController',
$this->getQueryRoutePattern() => 'DifferentialRevisionListController',
'diff/' => array(
'(?P<id>[1-9]\d*)/' => array(
'' => 'DifferentialDiffViewController',

View file

@ -446,7 +446,18 @@ final class DifferentialInlineCommentMailView
'style' => implode(' ', $link_style),
'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 {
$link = null;
}

View file

@ -289,4 +289,77 @@ final class DifferentialRevisionSearchEngine
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;
}
}

View file

@ -26,7 +26,8 @@ final class PhabricatorBulkManagementExportWorkflow
'name' => 'query',
'param' => 'key',
'help' => pht(
'Export the data selected by this query.'),
'Export the data selected by one or more queries.'),
'repeat' => true,
),
array(
'name' => 'output',
@ -47,56 +48,7 @@ final class PhabricatorBulkManagementExportWorkflow
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$class = $args->getArg('class');
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));
}
list($engine, $queries) = $this->newQueries($args);
$format_key = $args->getArg('format');
if (!strlen($format_key)) {
@ -125,14 +77,27 @@ final class PhabricatorBulkManagementExportWorkflow
$is_overwrite = $args->getArg('overwrite');
$output_path = $args->getArg('output');
if (!strlen($output_path) && $is_overwrite) {
if (!strlen($output_path)) {
throw new PhutilArgumentUsageException(
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 (Filesystem::pathExists($output_path)) {
if (!$is_stdout && Filesystem::pathExists($output_path)) {
throw new PhutilArgumentUsageException(
pht(
'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())
->setViewer($viewer)
->setTitle(pht('Export'))
@ -152,10 +126,20 @@ final class PhabricatorBulkManagementExportWorkflow
$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) {
Filesystem::appendFile($output_path, $chunk);
}
echo tsprintf(
"%s\n",
pht(
'Exported data to "%s".',
Filesystem::readablePath($output_path)));
} else {
foreach ($iterator as $chunk) {
echo $chunk;
@ -165,4 +149,179 @@ final class PhabricatorBulkManagementExportWorkflow
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);
}
}

View file

@ -125,7 +125,7 @@ final class PhabricatorExportEngine
$field_list = mpull($field_list, null, 'getKey');
$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.
$page_size = 1000;
$page_cursor = null;