mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-12 18:02:39 +01:00
cd19216ea2
Summary: Ref T13546. Currently, each "land" workflow executes custom graph queries to find commits: move toward abstracting this logic. The "land" workflow also has a potentially dangerous behavior: if you have "master > A > B > C" and "arc land C", it will land A, B, and C. However, an updated version of A or B may exist elsewhere in the working copy. If it does, "arc land" will incorrectly land an out-of-date set of changes. To find newer versions of "A" and "B", we need to search backwards from all local markers to the nearest outgoing marker, then compare the sets of changes we find to the sets of changes selected by "arc land". This is also roughly the workflow that "arc branches", etc., need to show local markers as a tree, and starting in "arc branches" allows the process to be visualized. As implemented here ,this rendering is still somewhat rough, and the selection of "outgoing markers" isn't good. In Mercurial, we may plausibly be able to use phase markers, but in Git we likely can't guess the right behavior automatically and probably need additional configuration. Test Plan: Ran "arc branches" and "arc bookmarks" in Git and Mercurial. Maniphest Tasks: T13546 Differential Revision: https://secure.phabricator.com/D21363
302 lines
7.6 KiB
PHP
302 lines
7.6 KiB
PHP
<?php
|
|
|
|
abstract class ArcanistMarkersWorkflow
|
|
extends ArcanistArcWorkflow {
|
|
|
|
private $nodes;
|
|
|
|
abstract protected function getWorkflowMarkerType();
|
|
|
|
public function runWorkflow() {
|
|
$api = $this->getRepositoryAPI();
|
|
|
|
$marker_type = $this->getWorkflowMarkerType();
|
|
|
|
$markers = $api->newMarkerRefQuery()
|
|
->withMarkerTypes(array($marker_type))
|
|
->execute();
|
|
|
|
$tail_hashes = $this->getTailHashes();
|
|
|
|
$heads = mpull($markers, 'getCommitHash');
|
|
|
|
$graph = $api->getGraph();
|
|
$limit = 1000;
|
|
|
|
$query = $graph->newQuery()
|
|
->withHeadHashes($heads)
|
|
->setLimit($limit + 1);
|
|
|
|
if ($tail_hashes) {
|
|
$query->withTailHashes($tail_hashes);
|
|
}
|
|
|
|
$nodes = $query->execute();
|
|
|
|
if (count($nodes) > $limit) {
|
|
|
|
// TODO: Show what we can.
|
|
|
|
throw new PhutilArgumentUsageException(
|
|
pht(
|
|
'Found more than %s unpublished commits which are ancestors of '.
|
|
'heads.',
|
|
new PhutilNumber($limit)));
|
|
}
|
|
|
|
// We may have some markers which point at commits which are already
|
|
// published. These markers won't be reached by following heads backwards
|
|
// until we reach published commits.
|
|
|
|
// Load these markers exactly so they don't vanish in the output.
|
|
|
|
// TODO: Mark these sets as published.
|
|
|
|
$disjoint_heads = array();
|
|
foreach ($heads as $head) {
|
|
if (!isset($nodes[$head])) {
|
|
$disjoint_heads[] = $head;
|
|
}
|
|
}
|
|
|
|
if ($disjoint_heads) {
|
|
|
|
// TODO: Git currently can not query for more than one exact hash at a
|
|
// time.
|
|
|
|
foreach ($disjoint_heads as $disjoint_head) {
|
|
$disjoint_nodes = $graph->newQuery()
|
|
->withExactHashes(array($disjoint_head))
|
|
->execute();
|
|
|
|
$nodes += $disjoint_nodes;
|
|
}
|
|
}
|
|
|
|
$state_refs = array();
|
|
foreach ($nodes as $node) {
|
|
$commit_ref = $node->getCommitRef();
|
|
|
|
$state_ref = id(new ArcanistWorkingCopyStateRef())
|
|
->setCommitRef($commit_ref);
|
|
|
|
$state_refs[$node->getCommitHash()] = $state_ref;
|
|
}
|
|
|
|
$this->loadHardpoints(
|
|
$state_refs,
|
|
ArcanistWorkingCopyStateRef::HARDPOINT_REVISIONREFS);
|
|
|
|
$partitions = $graph->newPartitionQuery()
|
|
->withHeads($heads)
|
|
->withHashes(array_keys($nodes))
|
|
->execute();
|
|
|
|
$revision_refs = array();
|
|
foreach ($state_refs as $hash => $state_ref) {
|
|
$revision_ids = mpull($state_ref->getRevisionRefs(), 'getID');
|
|
$revision_refs[$hash] = array_fuse($revision_ids);
|
|
}
|
|
|
|
$partition_sets = array();
|
|
$partition_vectors = array();
|
|
foreach ($partitions as $partition_key => $partition) {
|
|
$sets = $partition->newSetQuery()
|
|
->setWaypointMap($revision_refs)
|
|
->execute();
|
|
|
|
list($sets, $partition_vector) = $this->sortSets(
|
|
$graph,
|
|
$sets,
|
|
$markers);
|
|
|
|
$partition_sets[$partition_key] = $sets;
|
|
$partition_vectors[$partition_key] = $partition_vector;
|
|
}
|
|
|
|
$partition_vectors = msortv($partition_vectors, 'getSelf');
|
|
$partitions = array_select_keys(
|
|
$partitions,
|
|
array_keys($partition_vectors));
|
|
|
|
$partition_lists = array();
|
|
foreach ($partitions as $partition_key => $partition) {
|
|
$sets = $partition_sets[$partition_key];
|
|
|
|
$roots = array();
|
|
foreach ($sets as $set) {
|
|
if (!$set->getParentSets()) {
|
|
$roots[] = $set;
|
|
}
|
|
}
|
|
|
|
// TODO: When no parent of a set is in the node list, we should render
|
|
// a marker showing that the commit sequence is historic.
|
|
|
|
$row_lists = array();
|
|
foreach ($roots as $set) {
|
|
$view = id(new ArcanistCommitGraphSetTreeView())
|
|
->setRepositoryAPI($api)
|
|
->setRootSet($set)
|
|
->setMarkers($markers)
|
|
->setStateRefs($state_refs);
|
|
|
|
$row_lists[] = $view->draw();
|
|
}
|
|
$partition_lists[] = $row_lists;
|
|
}
|
|
|
|
$grid = id(new ArcanistGridView());
|
|
$grid->newColumn('marker');
|
|
$grid->newColumn('commits');
|
|
$grid->newColumn('status');
|
|
$grid->newColumn('revisions');
|
|
$grid->newColumn('messages');
|
|
|
|
foreach ($partition_lists as $row_lists) {
|
|
foreach ($row_lists as $row_list) {
|
|
foreach ($row_list as $row) {
|
|
$grid->newRow($row);
|
|
}
|
|
}
|
|
}
|
|
|
|
echo tsprintf('%s', $grid->drawGrid());
|
|
}
|
|
|
|
final protected function hasMarkerTypeSupport($marker_type) {
|
|
$api = $this->getRepositoryAPI();
|
|
|
|
$types = $api->getSupportedMarkerTypes();
|
|
$types = array_fuse($types);
|
|
|
|
return isset($types[$marker_type]);
|
|
}
|
|
|
|
private function getTailHashes() {
|
|
$api = $this->getRepositoryAPI();
|
|
return $api->getPublishedCommitHashes();
|
|
}
|
|
|
|
private function sortSets(
|
|
ArcanistCommitGraph $graph,
|
|
array $sets,
|
|
array $markers) {
|
|
|
|
$marker_groups = mgroup($markers, 'getCommitHash');
|
|
$sets = mpull($sets, null, 'getSetID');
|
|
|
|
$active_markers = array();
|
|
foreach ($sets as $set_id => $set) {
|
|
foreach ($set->getHashes() as $hash) {
|
|
$markers = idx($marker_groups, $hash, array());
|
|
|
|
$has_active = false;
|
|
foreach ($markers as $marker) {
|
|
if ($marker->getIsActive()) {
|
|
$has_active = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($has_active) {
|
|
$active_markers[$set_id] = $set;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$stack = array_select_keys($sets, array_keys($active_markers));
|
|
while ($stack) {
|
|
$cursor = array_pop($stack);
|
|
foreach ($cursor->getParentSets() as $parent_id => $parent) {
|
|
if (isset($active_markers[$parent_id])) {
|
|
continue;
|
|
}
|
|
$active_markers[$parent_id] = $parent;
|
|
$stack[] = $parent;
|
|
}
|
|
}
|
|
|
|
$partition_epoch = 0;
|
|
$partition_names = array();
|
|
|
|
$vectors = array();
|
|
foreach ($sets as $set_id => $set) {
|
|
if (isset($active_markers[$set_id])) {
|
|
$has_active = 1;
|
|
} else {
|
|
$has_active = 0;
|
|
}
|
|
|
|
$max_epoch = 0;
|
|
$marker_names = array();
|
|
foreach ($set->getHashes() as $hash) {
|
|
$node = $graph->getNode($hash);
|
|
$max_epoch = max($max_epoch, $node->getCommitEpoch());
|
|
|
|
$markers = idx($marker_groups, $hash, array());
|
|
foreach ($markers as $marker) {
|
|
$marker_names[] = $marker->getName();
|
|
}
|
|
}
|
|
|
|
$partition_epoch = max($partition_epoch, $max_epoch);
|
|
|
|
if ($marker_names) {
|
|
$has_markers = 1;
|
|
natcasesort($marker_names);
|
|
$max_name = last($marker_names);
|
|
|
|
$partition_names[] = $max_name;
|
|
} else {
|
|
$has_markers = 0;
|
|
$max_name = '';
|
|
}
|
|
|
|
|
|
$vector = id(new PhutilSortVector())
|
|
->addInt($has_active)
|
|
->addInt($max_epoch)
|
|
->addInt($has_markers)
|
|
->addString($max_name);
|
|
|
|
$vectors[$set_id] = $vector;
|
|
}
|
|
|
|
$vectors = msortv_natural($vectors, 'getSelf');
|
|
$vector_keys = array_keys($vectors);
|
|
|
|
foreach ($sets as $set_id => $set) {
|
|
$child_sets = $set->getDisplayChildSets();
|
|
$child_sets = array_select_keys($child_sets, $vector_keys);
|
|
$set->setDisplayChildSets($child_sets);
|
|
}
|
|
|
|
$sets = array_select_keys($sets, $vector_keys);
|
|
|
|
if ($active_markers) {
|
|
$any_active = true;
|
|
} else {
|
|
$any_active = false;
|
|
}
|
|
|
|
if ($partition_names) {
|
|
$has_markers = 1;
|
|
natcasesort($partition_names);
|
|
$partition_name = last($partition_names);
|
|
} else {
|
|
$has_markers = 0;
|
|
$partition_name = '';
|
|
}
|
|
|
|
$partition_vector = id(new PhutilSortVector())
|
|
->addInt($any_active)
|
|
->addInt($partition_epoch)
|
|
->addInt($has_markers)
|
|
->addString($partition_name);
|
|
|
|
return array($sets, $partition_vector);
|
|
}
|
|
|
|
}
|