mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-10 00:42:40 +01:00
Support date-range commit graph queries, and multiple disjoint commits in Git
Summary: Ref T13546. Allow the commit graph to be queried by date range, and Git to be queried for multiple disjoint commits. Test Plan: Ran "arc branches" and future code which searches for alternate commit ranges for revisions. Maniphest Tasks: T13546 Differential Revision: https://secure.phabricator.com/D21379
This commit is contained in:
parent
c7093a2e57
commit
8c95dc0d29
5 changed files with 172 additions and 69 deletions
|
@ -70,6 +70,11 @@ final class ArcanistRevisionRef
|
||||||
$status_value = idx($value_map, idx($dict, 'status'));
|
$status_value = idx($value_map, idx($dict, 'status'));
|
||||||
$ansi_color = idx($color_map, $status_value);
|
$ansi_color = idx($color_map, $status_value);
|
||||||
|
|
||||||
|
$date_created = null;
|
||||||
|
if (isset($dict['dateCreated'])) {
|
||||||
|
$date_created = (int)$dict['dateCreated'];
|
||||||
|
}
|
||||||
|
|
||||||
$dict['fields'] = array(
|
$dict['fields'] = array(
|
||||||
'uri' => idx($dict, 'uri'),
|
'uri' => idx($dict, 'uri'),
|
||||||
'title' => idx($dict, 'title'),
|
'title' => idx($dict, 'title'),
|
||||||
|
@ -80,6 +85,7 @@ final class ArcanistRevisionRef
|
||||||
'value' => $status_value,
|
'value' => $status_value,
|
||||||
'color.ansi' => $ansi_color,
|
'color.ansi' => $ansi_color,
|
||||||
),
|
),
|
||||||
|
'dateCreated' => $date_created,
|
||||||
);
|
);
|
||||||
|
|
||||||
return self::newFromConduit($dict);
|
return self::newFromConduit($dict);
|
||||||
|
@ -104,6 +110,10 @@ final class ArcanistRevisionRef
|
||||||
return idxv($this->parameters, array('fields', 'status', 'color.ansi'));
|
return idxv($this->parameters, array('fields', 'status', 'color.ansi'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDateCreated() {
|
||||||
|
return idxv($this->parameters, array('fields', 'dateCreated'));
|
||||||
|
}
|
||||||
|
|
||||||
public function isStatusChangesPlanned() {
|
public function isStatusChangesPlanned() {
|
||||||
$status = $this->getStatus();
|
$status = $this->getStatus();
|
||||||
return ($status === 'changes-planned');
|
return ($status === 'changes-planned');
|
||||||
|
|
|
@ -7,8 +7,9 @@ abstract class ArcanistCommitGraphQuery
|
||||||
private $headHashes;
|
private $headHashes;
|
||||||
private $tailHashes;
|
private $tailHashes;
|
||||||
private $exactHashes;
|
private $exactHashes;
|
||||||
private $stopAtGCA;
|
|
||||||
private $limit;
|
private $limit;
|
||||||
|
private $minimumEpoch;
|
||||||
|
private $maximumEpoch;
|
||||||
|
|
||||||
final public function setGraph(ArcanistCommitGraph $graph) {
|
final public function setGraph(ArcanistCommitGraph $graph) {
|
||||||
$this->graph = $graph;
|
$this->graph = $graph;
|
||||||
|
@ -46,11 +47,6 @@ abstract class ArcanistCommitGraphQuery
|
||||||
return $this->exactHashes;
|
return $this->exactHashes;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function withStopAtGCA($stop_gca) {
|
|
||||||
$this->stopAtGCA = $stop_gca;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
final public function setLimit($limit) {
|
final public function setLimit($limit) {
|
||||||
$this->limit = $limit;
|
$this->limit = $limit;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -60,6 +56,20 @@ abstract class ArcanistCommitGraphQuery
|
||||||
return $this->limit;
|
return $this->limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final public function withEpochRange($min, $max) {
|
||||||
|
$this->minimumEpoch = $min;
|
||||||
|
$this->maximumEpoch = $max;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function getMinimumEpoch() {
|
||||||
|
return $this->minimumEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function getMaximumEpoch() {
|
||||||
|
return $this->maximumEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
final public function getRepositoryAPI() {
|
final public function getRepositoryAPI() {
|
||||||
return $this->getGraph()->getRepositoryAPI();
|
return $this->getGraph()->getRepositoryAPI();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,21 @@
|
||||||
final class ArcanistGitCommitGraphQuery
|
final class ArcanistGitCommitGraphQuery
|
||||||
extends ArcanistCommitGraphQuery {
|
extends ArcanistCommitGraphQuery {
|
||||||
|
|
||||||
private $queryFuture;
|
|
||||||
private $seen = array();
|
private $seen = array();
|
||||||
|
private $futures = array();
|
||||||
|
private $iterators = array();
|
||||||
|
private $cursors = array();
|
||||||
|
private $iteratorKey = 0;
|
||||||
|
|
||||||
public function execute() {
|
public function execute() {
|
||||||
$this->beginExecute();
|
$this->newFutures();
|
||||||
$this->continueExecute();
|
|
||||||
|
$this->executeIterators();
|
||||||
|
|
||||||
return $this->seen;
|
return $this->seen;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function beginExecute() {
|
private function newFutures() {
|
||||||
$head_hashes = $this->getHeadHashes();
|
$head_hashes = $this->getHeadHashes();
|
||||||
$exact_hashes = $this->getExactHashes();
|
$exact_hashes = $this->getExactHashes();
|
||||||
|
|
||||||
|
@ -22,72 +26,122 @@ final class ArcanistGitCommitGraphQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
$api = $this->getRepositoryAPI();
|
$api = $this->getRepositoryAPI();
|
||||||
|
$ref_lists = array();
|
||||||
|
|
||||||
$refs = array();
|
if ($head_hashes) {
|
||||||
if ($head_hashes !== null) {
|
$refs = array();
|
||||||
foreach ($head_hashes as $hash) {
|
if ($head_hashes !== null) {
|
||||||
$refs[] = $hash;
|
foreach ($head_hashes as $hash) {
|
||||||
|
$refs[] = $hash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$tail_hashes = $this->getTailHashes();
|
$tail_hashes = $this->getTailHashes();
|
||||||
if ($tail_hashes !== null) {
|
if ($tail_hashes !== null) {
|
||||||
foreach ($tail_hashes as $tail_hash) {
|
foreach ($tail_hashes as $tail_hash) {
|
||||||
$refs[] = sprintf('^%s^@', $tail_hash);
|
$refs[] = sprintf('^%s^@', $tail_hash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ref_lists[] = $refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($exact_hashes !== null) {
|
if ($exact_hashes !== null) {
|
||||||
if (count($exact_hashes) > 1) {
|
|
||||||
// If "A" is a parent of "B" and we search for exact hashes ["A", "B"],
|
|
||||||
// the exclusion rule generated by "^B^@" is stronger than the inclusion
|
|
||||||
// rule generated by "A" and we don't get "A" in the result set.
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'TODO: Multiple exact hashes not supported under Git.'));
|
|
||||||
}
|
|
||||||
foreach ($exact_hashes as $exact_hash) {
|
foreach ($exact_hashes as $exact_hash) {
|
||||||
$refs[] = $exact_hash;
|
$ref_list = array();
|
||||||
$refs[] = sprintf('^%s^@', $exact_hash);
|
$ref_list[] = $exact_hash;
|
||||||
|
$ref_list[] = sprintf('^%s^@', $exact_hash);
|
||||||
|
$ref_list[] = '--';
|
||||||
|
$ref_lists[] = $ref_list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$refs[] = '--';
|
$flags = array();
|
||||||
$refs = implode("\n", $refs)."\n";
|
|
||||||
|
|
||||||
$fields = array(
|
$min_epoch = $this->getMinimumEpoch();
|
||||||
'%e',
|
if ($min_epoch !== null) {
|
||||||
'%H',
|
$flags[] = '--after';
|
||||||
'%P',
|
$flags[] = date('c', $min_epoch);
|
||||||
'%ct',
|
}
|
||||||
'%B',
|
|
||||||
);
|
|
||||||
|
|
||||||
$format = implode('%x02', $fields).'%x01';
|
$max_epoch = $this->getMaximumEpoch();
|
||||||
|
if ($max_epoch !== null) {
|
||||||
|
$flags[] = '--before';
|
||||||
|
$flags[] = date('c', $max_epoch);
|
||||||
|
}
|
||||||
|
|
||||||
$future = $api->newFuture(
|
foreach ($ref_lists as $ref_list) {
|
||||||
'log --format=%s --stdin',
|
$ref_blob = implode("\n", $ref_list)."\n";
|
||||||
$format);
|
|
||||||
$future->write($refs);
|
|
||||||
$future->setResolveOnError(true);
|
|
||||||
$future->start();
|
|
||||||
|
|
||||||
$lines = id(new LinesOfALargeExecFuture($future))
|
$fields = array(
|
||||||
->setDelimiter("\1");
|
'%e',
|
||||||
$lines->rewind();
|
'%H',
|
||||||
|
'%P',
|
||||||
|
'%ct',
|
||||||
|
'%B',
|
||||||
|
);
|
||||||
|
|
||||||
$this->queryFuture = $lines;
|
$format = implode('%x02', $fields).'%x01';
|
||||||
|
|
||||||
|
$future = $api->newFuture(
|
||||||
|
'log --format=%s %Ls --stdin',
|
||||||
|
$format,
|
||||||
|
$flags);
|
||||||
|
$future->write($ref_blob);
|
||||||
|
$future->setResolveOnError(true);
|
||||||
|
|
||||||
|
$this->futures[] = $future;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function continueExecute() {
|
private function executeIterators() {
|
||||||
|
while ($this->futures || $this->iterators) {
|
||||||
|
$iterator_limit = 8;
|
||||||
|
|
||||||
|
while (count($this->iterators) < $iterator_limit) {
|
||||||
|
if (!$this->futures) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$future = array_pop($this->futures);
|
||||||
|
$future->startFuture();
|
||||||
|
|
||||||
|
$iterator = id(new LinesOfALargeExecFuture($future))
|
||||||
|
->setDelimiter("\1");
|
||||||
|
$iterator->rewind();
|
||||||
|
|
||||||
|
$iterator_key = $this->getNextIteratorKey();
|
||||||
|
$this->iterators[$iterator_key] = $iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
$limit = $this->getLimit();
|
||||||
|
|
||||||
|
foreach ($this->iterators as $iterator_key => $iterator) {
|
||||||
|
$this->executeIterator($iterator_key, $iterator);
|
||||||
|
|
||||||
|
if ($limit) {
|
||||||
|
if (count($this->seen) >= $limit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNextIteratorKey() {
|
||||||
|
return $this->iteratorKey++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function executeIterator($iterator_key, $lines) {
|
||||||
$graph = $this->getGraph();
|
$graph = $this->getGraph();
|
||||||
$limit = $this->getLimit();
|
$limit = $this->getLimit();
|
||||||
|
|
||||||
$lines = $this->queryFuture;
|
$is_done = false;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!$lines->valid()) {
|
if (!$lines->valid()) {
|
||||||
return false;
|
$is_done = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$line = $lines->current();
|
$line = $lines->current();
|
||||||
|
@ -133,6 +187,7 @@ final class ArcanistGitCommitGraphQuery
|
||||||
|
|
||||||
$parent_nodes[$parent] = $parent_node;
|
$parent_nodes[$parent] = $parent_node;
|
||||||
$parent_node->addChildNode($node);
|
$parent_node->addChildNode($node);
|
||||||
|
|
||||||
}
|
}
|
||||||
$node->setParentNodes($parent_nodes);
|
$node->setParentNodes($parent_nodes);
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,6 +200,10 @@ final class ArcanistGitCommitGraphQuery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($is_done) {
|
||||||
|
unset($this->iterators[$iterator_key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ final class ArcanistMercurialCommitGraphQuery
|
||||||
|
|
||||||
if ($revsets) {
|
if ($revsets) {
|
||||||
$revsets = array(
|
$revsets = array(
|
||||||
$this->joinAndRevsets($revs),
|
$this->joinAndRevsets($revsets),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,12 +58,10 @@ final class ArcanistMercurialCommitGraphQuery
|
||||||
'%s',
|
'%s',
|
||||||
$exact_hash);
|
$exact_hash);
|
||||||
}
|
}
|
||||||
$revsets[] = array(
|
$revsets[] = $this->joinOrRevsets($revs);
|
||||||
$this->joinOrRevsets($revs),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$revsets = $this->joinOrRevsets($revs);
|
$revsets = $this->joinOrRevsets($revsets);
|
||||||
|
|
||||||
$fields = array(
|
$fields = array(
|
||||||
'', // Placeholder for "encoding".
|
'', // Placeholder for "encoding".
|
||||||
|
@ -75,10 +73,42 @@ final class ArcanistMercurialCommitGraphQuery
|
||||||
|
|
||||||
$template = implode("\2", $fields)."\1";
|
$template = implode("\2", $fields)."\1";
|
||||||
|
|
||||||
|
$flags = array();
|
||||||
|
|
||||||
|
$min_epoch = $this->getMinimumEpoch();
|
||||||
|
$max_epoch = $this->getMaximumEpoch();
|
||||||
|
if ($min_epoch !== null || $max_epoch !== null) {
|
||||||
|
$flags[] = '--date';
|
||||||
|
|
||||||
|
if ($min_epoch !== null) {
|
||||||
|
$min_epoch = date('c', $min_epoch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($max_epoch !== null) {
|
||||||
|
$max_epoch = date('c', $max_epoch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($min_epoch !== null && $max_epoch !== null) {
|
||||||
|
$flags[] = sprintf(
|
||||||
|
'%s to %s',
|
||||||
|
$min_epoch,
|
||||||
|
$max_epoch);
|
||||||
|
} else if ($min_epoch) {
|
||||||
|
$flags[] = sprintf(
|
||||||
|
'>%s',
|
||||||
|
$min_epoch);
|
||||||
|
} else {
|
||||||
|
$flags[] = sprintf(
|
||||||
|
'<%s',
|
||||||
|
$max_epoch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$future = $api->newFuture(
|
$future = $api->newFuture(
|
||||||
'log --rev %s --template %s --',
|
'log --rev %s --template %s %Ls --',
|
||||||
$revsets,
|
$revsets,
|
||||||
$template);
|
$template,
|
||||||
|
$flags);
|
||||||
$future->setResolveOnError(true);
|
$future->setResolveOnError(true);
|
||||||
$future->start();
|
$future->start();
|
||||||
|
|
||||||
|
|
|
@ -60,17 +60,11 @@ abstract class ArcanistMarkersWorkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($disjoint_heads) {
|
if ($disjoint_heads) {
|
||||||
|
$disjoint_nodes = $graph->newQuery()
|
||||||
|
->withExactHashes($disjoint_heads)
|
||||||
|
->execute();
|
||||||
|
|
||||||
// TODO: Git currently can not query for more than one exact hash at a
|
$nodes += $disjoint_nodes;
|
||||||
// time.
|
|
||||||
|
|
||||||
foreach ($disjoint_heads as $disjoint_head) {
|
|
||||||
$disjoint_nodes = $graph->newQuery()
|
|
||||||
->withExactHashes(array($disjoint_head))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$nodes += $disjoint_nodes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$state_refs = array();
|
$state_refs = array();
|
||||||
|
|
Loading…
Reference in a new issue