2011-06-23 21:10:59 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Displays user's git branches
|
|
|
|
*
|
|
|
|
* @group workflow
|
|
|
|
*/
|
2012-01-31 21:07:05 +01:00
|
|
|
final class ArcanistBranchWorkflow extends ArcanistBaseWorkflow {
|
2011-06-23 21:10:59 +02:00
|
|
|
|
|
|
|
private $branches;
|
|
|
|
|
Make Arcanist workflow names explicit
Summary:
Currently, adding a new workflow requires you to override ArcanistConfiguration, which is messy. Instead, just load everything that extends ArcanistBaseWorkflow.
Remove all the rules tying workflow names to class names through arcane incantations.
This has a very small performance cost in that we need to load every Workflow class every time now, but we don't hit __init__ and such anymore and it was pretty negligible on my machine (98ms vs 104ms or something).
Test Plan: Ran "arc help", "arc which", "arc diff", etc.
Reviewers: edward, vrana, btrahan
Reviewed By: edward
CC: aran, zeeg
Differential Revision: https://secure.phabricator.com/D3691
2012-10-17 17:35:03 +02:00
|
|
|
public function getWorkflowName() {
|
|
|
|
return 'branch';
|
|
|
|
}
|
|
|
|
|
2012-03-05 19:02:37 +01:00
|
|
|
public function getCommandSynopses() {
|
2011-06-23 21:10:59 +02:00
|
|
|
return phutil_console_format(<<<EOTEXT
|
2012-06-26 19:50:43 +02:00
|
|
|
**branch** [__options__]
|
2012-03-05 19:02:37 +01:00
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommandHelp() {
|
|
|
|
return phutil_console_format(<<<EOTEXT
|
2011-06-23 21:10:59 +02:00
|
|
|
Supports: git
|
|
|
|
A wrapper on 'git branch'. It pulls data from Differential and
|
|
|
|
displays the revision status next to the branch name.
|
2012-06-26 19:50:43 +02:00
|
|
|
|
|
|
|
By default, branches are sorted chronologically. You can sort them
|
|
|
|
by status instead with __--by-status__.
|
|
|
|
|
|
|
|
By default, branches that are "Closed" or "Abandoned" are not
|
|
|
|
displayed. You can show them with __--view-all__.
|
2011-06-23 21:10:59 +02:00
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function requiresConduit() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function requiresRepositoryAPI() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function requiresAuthentication() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function getArguments() {
|
|
|
|
return array(
|
|
|
|
'view-all' => array(
|
2012-06-26 19:50:43 +02:00
|
|
|
'help' => 'Include closed and abandoned revisions',
|
2011-06-23 21:10:59 +02:00
|
|
|
),
|
2011-06-24 18:16:49 +02:00
|
|
|
'by-status' => array(
|
2012-06-26 19:50:43 +02:00
|
|
|
'help' => 'Sort branches by status instead of time.',
|
2011-06-24 18:16:49 +02:00
|
|
|
),
|
2011-06-23 21:10:59 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function run() {
|
|
|
|
$repository_api = $this->getRepositoryAPI();
|
|
|
|
if (!($repository_api instanceof ArcanistGitAPI)) {
|
|
|
|
throw new ArcanistUsageException(
|
2012-06-26 19:50:43 +02:00
|
|
|
'arc branch is only supported under git.');
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
|
|
|
|
2012-06-26 19:50:43 +02:00
|
|
|
$branches = $repository_api->getAllBranches();
|
|
|
|
if (!$branches) {
|
|
|
|
throw new ArcanistUsageException('No branches in this working copy.');
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
2012-06-26 19:50:43 +02:00
|
|
|
|
2012-07-19 01:50:49 +02:00
|
|
|
$branches = $this->loadCommitInfo($branches, $repository_api);
|
2012-06-26 19:50:43 +02:00
|
|
|
|
|
|
|
$revisions = $this->loadRevisions($branches);
|
|
|
|
|
|
|
|
$this->printBranches($branches, $revisions);
|
|
|
|
|
|
|
|
return 0;
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
|
|
|
|
2012-06-26 19:50:43 +02:00
|
|
|
private function loadCommitInfo(
|
|
|
|
array $branches,
|
|
|
|
ArcanistRepositoryAPI $repository_api) {
|
|
|
|
|
2012-07-19 01:50:49 +02:00
|
|
|
$futures = array();
|
|
|
|
foreach ($branches as $branch) {
|
|
|
|
// NOTE: "-s" is an option deep in git's diff argument parser that doesn't
|
|
|
|
// seem to have much documentation and has no long form. It suppresses any
|
|
|
|
// diff output.
|
|
|
|
$futures[$branch['name']] = $repository_api->execFutureLocal(
|
2012-07-22 22:19:09 +02:00
|
|
|
'show -s --format=%C %s --',
|
2012-07-19 01:50:49 +02:00
|
|
|
'%H%x01%ct%x01%T%x01%s%x01%b',
|
|
|
|
$branch['name']);
|
|
|
|
}
|
2012-07-12 01:55:29 +02:00
|
|
|
|
2012-07-19 01:50:49 +02:00
|
|
|
$branches = ipull($branches, null, 'name');
|
2012-06-26 19:50:43 +02:00
|
|
|
|
2012-07-19 01:50:49 +02:00
|
|
|
foreach (Futures($futures) as $name => $future) {
|
|
|
|
list($info) = $future->resolvex();
|
|
|
|
list($hash, $epoch, $tree, $desc, $text) = explode("\1", trim($info), 5);
|
|
|
|
|
|
|
|
$branch = $branches[$name];
|
|
|
|
$branch['hash'] = $hash;
|
|
|
|
$branch['desc'] = $desc;
|
2011-06-23 21:10:59 +02:00
|
|
|
|
2012-07-09 21:05:35 +02:00
|
|
|
try {
|
2012-07-19 01:50:49 +02:00
|
|
|
$text = $desc."\n".$text;
|
2012-07-09 21:05:35 +02:00
|
|
|
$message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text);
|
|
|
|
$id = $message->getRevisionID();
|
|
|
|
|
2012-07-19 01:50:49 +02:00
|
|
|
$branch += array(
|
2012-07-09 21:05:35 +02:00
|
|
|
'epoch' => (int)$epoch,
|
|
|
|
'tree' => $tree,
|
|
|
|
'revisionID' => $id,
|
|
|
|
);
|
|
|
|
} catch (ArcanistUsageException $ex) {
|
2012-07-19 01:50:49 +02:00
|
|
|
// In case of invalid commit message which fails the parsing,
|
|
|
|
// do nothing.
|
2012-07-09 21:05:35 +02:00
|
|
|
}
|
2012-07-19 01:50:49 +02:00
|
|
|
|
2012-08-08 03:21:32 +02:00
|
|
|
$branches[$name] = $branch;
|
2012-06-26 19:50:43 +02:00
|
|
|
}
|
|
|
|
|
2012-08-08 03:21:32 +02:00
|
|
|
return $branches;
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
|
|
|
|
2012-06-26 19:50:43 +02:00
|
|
|
private function loadRevisions(array $branches) {
|
|
|
|
$ids = array();
|
|
|
|
$hashes = array();
|
|
|
|
|
|
|
|
foreach ($branches as $branch) {
|
|
|
|
if ($branch['revisionID']) {
|
|
|
|
$ids[] = $branch['revisionID'];
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
2012-06-26 19:50:43 +02:00
|
|
|
$hashes[] = array('gtcm', $branch['hash']);
|
|
|
|
$hashes[] = array('gttr', $branch['tree']);
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
|
|
|
|
2012-06-26 19:50:43 +02:00
|
|
|
$calls = array();
|
|
|
|
|
|
|
|
if ($ids) {
|
|
|
|
$calls[] = $this->getConduit()->callMethod(
|
|
|
|
'differential.query',
|
|
|
|
array(
|
|
|
|
'ids' => $ids,
|
|
|
|
));
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
2011-06-24 18:16:49 +02:00
|
|
|
|
2012-06-26 19:50:43 +02:00
|
|
|
if ($hashes) {
|
|
|
|
$calls[] = $this->getConduit()->callMethod(
|
|
|
|
'differential.query',
|
|
|
|
array(
|
|
|
|
'commitHashes' => $hashes,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
$results = array();
|
|
|
|
foreach (Futures($calls) as $call) {
|
|
|
|
$results[] = $call->resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_mergev($results);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function printBranches(array $branches, array $revisions) {
|
|
|
|
$revisions = ipull($revisions, null, 'id');
|
|
|
|
|
|
|
|
static $color_map = array(
|
|
|
|
'Closed' => 'cyan',
|
|
|
|
'Needs Review' => 'magenta',
|
|
|
|
'Needs Revision' => 'red',
|
|
|
|
'Accepted' => 'green',
|
|
|
|
'No Revision' => 'blue',
|
|
|
|
'Abandoned' => 'default',
|
|
|
|
);
|
|
|
|
|
|
|
|
static $ssort_map = array(
|
|
|
|
'Closed' => 1,
|
|
|
|
'No Revision' => 2,
|
|
|
|
'Needs Review' => 3,
|
|
|
|
'Needs Revision' => 4,
|
|
|
|
'Accepted' => 5,
|
|
|
|
);
|
|
|
|
|
|
|
|
$out = array();
|
|
|
|
foreach ($branches as $branch) {
|
|
|
|
$revision = idx($revisions, idx($branch, 'revisionID'));
|
|
|
|
|
|
|
|
// If we haven't identified a revision by ID, try to identify it by hash.
|
|
|
|
if (!$revision) {
|
|
|
|
foreach ($revisions as $rev) {
|
|
|
|
$hashes = idx($rev, 'hashes', array());
|
|
|
|
foreach ($hashes as $hash) {
|
|
|
|
if (($hash[0] == 'gtcm' && $hash[1] == $branch['hash']) ||
|
|
|
|
($hash[0] == 'gttr' && $hash[1] == $branch['tree'])) {
|
|
|
|
$revision = $rev;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-06-24 18:16:49 +02:00
|
|
|
}
|
2012-06-26 19:50:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($revision) {
|
|
|
|
$desc = 'D'.$revision['id'].': '.$revision['title'];
|
|
|
|
$status = $revision['statusName'];
|
|
|
|
} else {
|
|
|
|
$desc = $branch['desc'];
|
|
|
|
$status = 'No Revision';
|
|
|
|
}
|
|
|
|
|
2012-06-26 21:45:47 +02:00
|
|
|
if (!$this->getArgument('view-all') && !$branch['current']) {
|
2012-06-26 19:50:43 +02:00
|
|
|
if ($status == 'Closed' || $status == 'Abandoned') {
|
|
|
|
continue;
|
2011-06-24 18:16:49 +02:00
|
|
|
}
|
|
|
|
}
|
2012-06-26 19:50:43 +02:00
|
|
|
|
|
|
|
$epoch = $branch['epoch'];
|
|
|
|
|
|
|
|
$color = idx($color_map, $status, 'default');
|
|
|
|
$ssort = sprintf('%d%012d', idx($ssort_map, $status, 0), $epoch);
|
|
|
|
|
|
|
|
$out[] = array(
|
|
|
|
'name' => $branch['name'],
|
|
|
|
'current' => $branch['current'],
|
|
|
|
'status' => $status,
|
|
|
|
'desc' => $desc,
|
|
|
|
'color' => $color,
|
|
|
|
'esort' => $epoch,
|
|
|
|
'ssort' => $ssort,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$len_name = max(array_map('strlen', ipull($out, 'name'))) + 2;
|
|
|
|
$len_status = max(array_map('strlen', ipull($out, 'status'))) + 2;
|
|
|
|
|
|
|
|
if ($this->getArgument('by-status')) {
|
|
|
|
$out = isort($out, 'ssort');
|
2011-06-24 18:16:49 +02:00
|
|
|
} else {
|
2012-06-26 19:50:43 +02:00
|
|
|
$out = isort($out, 'esort');
|
|
|
|
}
|
|
|
|
|
|
|
|
$console = PhutilConsole::getConsole();
|
|
|
|
foreach ($out as $line) {
|
|
|
|
$color = $line['color'];
|
|
|
|
$console->writeOut(
|
|
|
|
"%s **%s** <fg:{$color}>%s</fg> %s\n",
|
|
|
|
$line['current'] ? '* ' : ' ',
|
|
|
|
str_pad($line['name'], $len_name),
|
|
|
|
str_pad($line['status'], $len_status),
|
|
|
|
$line['desc']);
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|
|
|
|
}
|
2012-06-26 19:50:43 +02:00
|
|
|
|
2011-06-23 21:10:59 +02:00
|
|
|
}
|