1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-12-26 15:30:57 +01:00

Add an arc flag workflow

Summary:
Allow querying and modifying flags from arcanist. Currently
supports only printing and deleting flags for Differential revisions,
but it should be straightforward to add more capabilities (given Conduit
support).

Test Plan:
Run arc flag, passing it various revisions. Flags are
modified appropriately.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T1556

Differential Revision: https://secure.phabricator.com/D3133
This commit is contained in:
Alan Huang 2012-08-03 12:00:54 -07:00
parent 4593af23a7
commit 877e6f743b
2 changed files with 233 additions and 0 deletions

View file

@ -50,6 +50,7 @@ phutil_register_library_map(array(
'ArcanistEventType' => 'events/constant/ArcanistEventType.php',
'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php',
'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php',
'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php',
'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php',
'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php',
'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php',
@ -168,6 +169,7 @@ phutil_register_library_map(array(
'ArcanistEventType' => 'PhutilEventType',
'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistFilenameLinter' => 'ArcanistLinter',
'ArcanistFlagWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistGeneratedLinter' => 'ArcanistLinter',
'ArcanistGetConfigWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistGitAPI' => 'ArcanistRepositoryAPI',

View file

@ -0,0 +1,231 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group workflow
*/
final class ArcanistFlagWorkflow extends ArcanistBaseWorkflow {
private static $colorMap = array(
0 => 'red', // Red
1 => 'yellow', // Orange
2 => 'yellow', // Yellow
3 => 'green', // Green
4 => 'blue', // Blue
5 => 'magenta', // Pink
6 => 'magenta', // Purple
7 => 'default', // Checkered
);
private static $colorSpec = array(
'red' => 0, 'r' => 0, 0 => 0,
'orange' => 1, 'o' => 1, 1 => 1,
'yellow' => 2, 'y' => 2, 2 => 2,
'green' => 3, 'g' => 3, 3 => 3,
'blue' => 4, 'b' => 4, 4 => 4,
'pink' => 5, 'p' => 5, 5 => 5,
'purple' => 6, 'v' => 6, 6 => 6,
'checkered' => 7, 'c' => 7, 7 => 7,
);
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**flag** [__object__ ...]
**flag** __object__ --clear
**flag** __object__ [--edit] [--color __color__] [--note __note__]
EOTEXT
);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
In the first form, list objects you've flagged. You can provide the
names of one or more objects (Maniphest tasks T#\##, Differential
revisions D###, Diffusion references rXXX???, or PHIDs PHID-XXX-???)
to print only flags for those objects.
In the second form, clear an existing flag on one object.
In the third form, create or update a flag on one object. Color
defaults to blue and note to empty, but if you omit both you must
pass --edit.
EOTEXT
);
}
public function getArguments() {
return array(
'*' => 'objects',
'clear' => array(
'help' => 'Delete the flag on an object.'
),
'edit' => array(
'help' => 'Edit the flag on an object.'
),
'color' => array(
'param' => 'color',
'help' => 'Set the color of a flag.'
),
'note' => array(
'param' => 'note',
'help' => 'Set the note on a flag.'
),
);
}
public function requiresConduit() {
return true;
}
public function requiresAuthentication() {
return true;
}
private static function flagWasEdited($flag, $verb) {
$color = idx(self::$colorMap, $flag['color'], 'cyan');
$note = $flag['note'];
if ($note) {
// Make sure notes that are long or have line breaks in them or
// whatever don't mess up the formatting.
$note = implode(' ', preg_split('/\s+/', $note));
$note = ' ('.phutil_utf8_shorten($note, 40, '...').')';
}
echo phutil_console_format(
"<fg:{$color}>%s</fg> flag%s $verb!\n",
$flag['colorName'],
$note);
}
public function run() {
$conduit = $this->getConduit();
$objects = $this->getArgument('objects', array());
$phids = array();
$clear = $this->getArgument('clear');
$edit = $this->getArgument('edit');
// I don't trust PHP to distinguish 0 (red) from null.
$color = $this->getArgument('color', -1);
$note = $this->getArgument('note');
$editing = $edit || ($color != -1) || $note;
if ($editing && $clear) {
throw new ArcanistUsageException("You can't both edit and clear a flag.");
}
if (($editing || $clear) && count($objects) != 1) {
throw new ArcanistUsageException("Specify exactly one object.");
}
if (!empty($objects)) {
// First off, convert the passed objects to PHIDs.
$handles = $conduit->callMethodSynchronous(
'phid.lookup',
array(
'names' => $objects,
));
foreach ($objects as $object) {
if (isset($handles[$object])) {
$phids[$object] = $handles[$object]['phid'];
} else {
echo phutil_console_format("**%s** doesn't exist.\n", $object);
}
}
if (empty($phids)) {
// flag.query treats an empty objectPHIDs parameter as "don't use this
// constraint". However, if the user gives a list of objects but none
// of them exist and have flags, we shouldn't dump the full list on
// them after telling them that. Conveniently, we already told them,
// so we can go quit now.
return 0;
}
}
if ($clear) {
// All right, we're going to clear a flag. First clear it. Then tell the
// user we cleared it. Step four: profit!
$flag = $conduit->callMethodSynchronous(
'flag.delete',
array(
'objectPHID' => head($phids),
));
if (!$flag) {
echo phutil_console_format("**%s** has no flag to clear.\n", $object);
} else {
self::flagWasEdited($flag, 'deleted');
}
} elseif ($editing) {
// Let's set some flags. Just like Minesweeper, but less distracting.
$flag_params = array(
'objectPHID' => head($phids),
);
if (isset(self::$colorSpec[$color])) {
$flag_params['color'] = self::$colorSpec[strtolower($color)];
}
if ($note) {
$flag_params['note'] = $note;
}
$flag = $conduit->callMethodSynchronous(
'flag.edit',
$flag_params
);
self::flagWasEdited($flag, $flag['new'] ? 'created' : 'edited');
} else {
// Okay, list mode. Let's find the flags, which we didn't need to do
// otherwise because Conduit does it for us.
$flags = ipull(
$this->getConduit()->callMethodSynchronous(
'flag.query',
array(
'ownerPHIDs' => array($this->getUserPHID()),
'objectPHIDs' => array_values($phids),
)),
null,
'objectPHID');
foreach ($phids as $object => $phid) {
if (!isset($flags[$phid])) {
echo phutil_console_format("**%s** has no flag.\n", $object);
}
}
if (empty($flags)) {
// If the user passed no object names, then we should print the full
// list, but it's empty, so tell the user they have no flags.
// If the user passed object names, we already told them all their
// objects are nonexistent or unflagged.
if (empty($objects)) {
echo "You have no flagged objects.\n";
}
} else {
// Print ALL the flags. With fancy formatting. Because fancy formatting
// is _cool_.
$name_len = 1 + max(array_map('strlen', ipull($flags, 'colorName')));
foreach ($flags as $flag) {
$color = idx(self::$colorMap, $flag['color'], 'cyan');
echo phutil_console_format(
"[<fg:{$color}>%s</fg>] %s\n",
str_pad($flag['colorName'], $name_len),
$flag['handle']['fullname']);
if ($flag['note']) {
$note = phutil_console_wrap($flag['note'], $name_len + 3);
echo rtrim($note)."\n";
}
}
}
}
}
}