Changeset View
Changeset View
Standalone View
Standalone View
src/workflow/ArcanistMarkersWorkflow.php
<?php | <?php | ||||
abstract class ArcanistMarkersWorkflow | abstract class ArcanistMarkersWorkflow | ||||
extends ArcanistArcWorkflow { | extends ArcanistArcWorkflow { | ||||
private $nodes; | |||||
abstract protected function getWorkflowMarkerType(); | abstract protected function getWorkflowMarkerType(); | ||||
public function runWorkflow() { | public function runWorkflow() { | ||||
$api = $this->getRepositoryAPI(); | $api = $this->getRepositoryAPI(); | ||||
$marker_type = $this->getWorkflowMarkerType(); | $marker_type = $this->getWorkflowMarkerType(); | ||||
$markers = $api->newMarkerRefQuery() | $markers = $api->newMarkerRefQuery() | ||||
->withMarkerTypes(array($marker_type)) | ->withMarkerTypes(array($marker_type)) | ||||
->execute(); | ->execute(); | ||||
$states = array(); | $tail_hashes = $this->getTailHashes(); | ||||
foreach ($markers as $marker) { | |||||
$state_ref = id(new ArcanistWorkingCopyStateRef()) | $heads = mpull($markers, 'getCommitHash'); | ||||
->setCommitRef($marker->getCommitRef()); | |||||
$graph = $api->getGraph(); | |||||
$limit = 1000; | |||||
$query = $graph->newQuery() | |||||
->withHeadHashes($heads) | |||||
->setLimit($limit + 1); | |||||
$states[] = array( | if ($tail_hashes) { | ||||
'marker' => $marker, | $query->withTailHashes($tail_hashes); | ||||
'state' => $state_ref, | |||||
); | |||||
} | } | ||||
$this->loadHardpoints( | $nodes = $query->execute(); | ||||
ipull($states, 'state'), | |||||
ArcanistWorkingCopyStateRef::HARDPOINT_REVISIONREFS); | |||||
$vectors = array(); | if (count($nodes) > $limit) { | ||||
foreach ($states as $key => $state) { | |||||
$marker_ref = $state['marker']; | |||||
$state_ref = $state['state']; | |||||
$vector = id(new PhutilSortVector()) | // TODO: Show what we can. | ||||
->addInt($marker_ref->getIsActive() ? 1 : 0) | |||||
->addInt($marker_ref->getEpoch()); | |||||
$vectors[$key] = $vector; | throw new PhutilArgumentUsageException( | ||||
pht( | |||||
'Found more than %s unpublished commits which are ancestors of '. | |||||
'heads.', | |||||
new PhutilNumber($limit))); | |||||
} | } | ||||
$vectors = msortv($vectors, 'getSelf'); | // We may have some markers which point at commits which are already | ||||
$states = array_select_keys($states, array_keys($vectors)); | // 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. | |||||
$table = id(new PhutilConsoleTable()) | $disjoint_heads = array(); | ||||
->setShowHeader(false) | foreach ($heads as $head) { | ||||
->addColumn('active') | if (!isset($nodes[$head])) { | ||||
->addColumn('name') | $disjoint_heads[] = $head; | ||||
->addColumn('status') | } | ||||
->addColumn('description'); | } | ||||
$rows = array(); | if ($disjoint_heads) { | ||||
foreach ($states as $state) { | |||||
$marker_ref = $state['marker']; | |||||
$state_ref = $state['state']; | |||||
$revision_ref = null; | |||||
$commit_ref = $marker_ref->getCommitRef(); | |||||
$marker_name = tsprintf('**%s**', $marker_ref->getName()); | // TODO: Git currently can not query for more than one exact hash at a | ||||
// time. | |||||
if ($state_ref->hasAmbiguousRevisionRefs()) { | foreach ($disjoint_heads as $disjoint_head) { | ||||
$status = pht('Ambiguous'); | $disjoint_nodes = $graph->newQuery() | ||||
} else { | ->withExactHashes(array($disjoint_head)) | ||||
$revision_ref = $state_ref->getRevisionRef(); | ->execute(); | ||||
if (!$revision_ref) { | |||||
$status = tsprintf( | |||||
'<fg:blue>%s</fg>', | |||||
pht('No Revision')); | |||||
} else { | |||||
$status = $revision_ref->getStatusDisplayName(); | |||||
$ansi_color = $revision_ref->getStatusANSIColor(); | $nodes += $disjoint_nodes; | ||||
if ($ansi_color) { | |||||
$status = tsprintf( | |||||
sprintf('<fg:%s>%%s</fg>', $ansi_color), | |||||
$status); | |||||
} | } | ||||
} | } | ||||
$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; | |||||
} | } | ||||
if ($revision_ref) { | $this->loadHardpoints( | ||||
$description = $revision_ref->getFullName(); | $state_refs, | ||||
} else { | ArcanistWorkingCopyStateRef::HARDPOINT_REVISIONREFS); | ||||
$description = $commit_ref->getSummary(); | |||||
$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); | |||||
} | } | ||||
if ($marker_ref->getIsActive()) { | $partition_sets = array(); | ||||
$active_mark = '*'; | $partition_vectors = array(); | ||||
} else { | foreach ($partitions as $partition_key => $partition) { | ||||
$active_mark = ' '; | $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; | |||||
} | } | ||||
$is_active = tsprintf('** %s **', $active_mark); | |||||
$rows[] = array( | $partition_vectors = msortv($partition_vectors, 'getSelf'); | ||||
'active' => $is_active, | $partitions = array_select_keys( | ||||
'name' => $marker_name, | $partitions, | ||||
'status' => $status, | array_keys($partition_vectors)); | ||||
'description' => $description, | |||||
); | $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. | |||||
$table->drawRows($rows); | $row_lists = array(); | ||||
foreach ($roots as $set) { | |||||
$view = id(new ArcanistCommitGraphSetTreeView()) | |||||
->setRepositoryAPI($api) | |||||
->setRootSet($set) | |||||
->setMarkers($markers) | |||||
->setStateRefs($state_refs); | |||||
return 0; | $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) { | final protected function hasMarkerTypeSupport($marker_type) { | ||||
$api = $this->getRepositoryAPI(); | $api = $this->getRepositoryAPI(); | ||||
$types = $api->getSupportedMarkerTypes(); | $types = $api->getSupportedMarkerTypes(); | ||||
$types = array_fuse($types); | $types = array_fuse($types); | ||||
return isset($types[$marker_type]); | 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); | |||||
} | |||||
} | } |