-
-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow running only actions specified on the command line #137
base: main
Are you sure you want to change the base?
Changes from all commits
24d6c0b
6ea2030
e65c88c
163f380
e29570e
e413409
3b64e0d
6acf735
c23b6ed
226072a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
use CaptainHook\App\Config; | ||
use CaptainHook\App\Console\IOUtil; | ||
use CaptainHook\App\Hook\Util; | ||
use CaptainHook\App\Runner\Hook as RunnerHook; | ||
use Exception; | ||
use RuntimeException; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
|
@@ -56,6 +57,50 @@ protected function configure(): void | |
InputOption::VALUE_OPTIONAL, | ||
'Relative path from your config file to your bootstrap file' | ||
); | ||
|
||
$this->addOption( | ||
'list-actions', | ||
'l', | ||
InputOption::VALUE_NONE, | ||
'List actions for this hook without running the hook' | ||
); | ||
|
||
$this->addOption( | ||
'action', | ||
'a', | ||
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, | ||
'Run only the actions listed' | ||
); | ||
|
||
$this->addOption( | ||
'disable-plugins', | ||
null, | ||
InputOption::VALUE_NONE, | ||
'Disable all hook plugins' | ||
); | ||
} | ||
|
||
/** | ||
* Initialize the command by checking/modifying inputs before validation | ||
* | ||
* @param \Symfony\Component\Console\Input\InputInterface $input | ||
* @param \Symfony\Component\Console\Output\OutputInterface $output | ||
* @return void | ||
*/ | ||
protected function initialize(InputInterface $input, OutputInterface $output): void | ||
{ | ||
// If `--list-actions` is present, we will ignore any arguments, since | ||
// this option intends to output a list of actions for the hook without | ||
// running the hook. So, if any arguments are required but not present | ||
// in the input, we will set them to an empty string in the input to | ||
// suppress any validation errors. | ||
if ($input->getOption('list-actions') === true) { | ||
foreach ($this->getDefinition()->getArguments() as $arg) { | ||
if ($arg->isRequired() && $input->getArgument($arg->getName()) === null) { | ||
$input->setArgument($arg->getName(), ''); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -74,15 +119,24 @@ protected function execute(InputInterface $input, OutputInterface $output): int | |
|
||
// use ansi coloring if not disabled in captainhook.json | ||
$output->setDecorated($config->useAnsiColors()); | ||
// use the configured verbosity to manage general output verbosity | ||
$output->setVerbosity(IOUtil::mapConfigVerbosity($config->getVerbosity())); | ||
|
||
// If the verbose option is present on the command line, then use it. | ||
// Otherwise, use the verbosity setting from the configuration. | ||
if (!$input->hasOption('verbose') || !$input->getOption('verbose')) { | ||
$output->setVerbosity(IOUtil::mapConfigVerbosity($config->getVerbosity())); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did this because I found that IMO, if you pass verbosity on the command line, it should override whatever is configured in |
||
|
||
try { | ||
$this->handleBootstrap($config); | ||
|
||
$class = '\\CaptainHook\\App\\Runner\\Hook\\' . Util::getHookCommand($this->hookName); | ||
/** @var \CaptainHook\App\Runner\Hook $hook */ | ||
$hook = new $class($io, $config, $repository); | ||
// If list-actions is true, then list the hook actions instead of running them. | ||
if ($input->getOption('list-actions') === true) { | ||
$this->listActions($output, $hook); | ||
return 0; | ||
} | ||
$hook->run(); | ||
return 0; | ||
} catch (Exception $e) { | ||
|
@@ -134,4 +188,30 @@ private function handleError(OutputInterface $output, Exception $e): int | |
|
||
return 1; | ||
} | ||
|
||
/** | ||
* Print out a list of actions for this hook | ||
* | ||
* @param OutputInterface $output | ||
* @param RunnerHook $hook | ||
*/ | ||
private function listActions(OutputInterface $output, RunnerHook $hook): void | ||
{ | ||
$output->writeln('<comment>Listing ' . $hook->getName() . ' actions:</comment>'); | ||
|
||
if (!$hook->isEnabled()) { | ||
$output->writeln(' - hook is disabled'); | ||
return; | ||
} | ||
|
||
$actions = $hook->getActions(); | ||
if (count($actions) === 0) { | ||
$output->writeln(' - no actions configured'); | ||
return; | ||
} | ||
|
||
foreach ($actions as $action) { | ||
$output->writeln(" - <fg=blue>{$action->getAction()}</>"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,6 +92,7 @@ public function execute(Config $config, IO $io, Repository $repository, Config\A | |
* Setup the action, reading and validating all config settings | ||
* | ||
* @param \CaptainHook\App\Config\Options $options | ||
* @codeCoverageIgnore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method does nothing here. It only has a comment in it, but it was showing up in coverage reports as an uncovered method. |
||
*/ | ||
protected function setUp(Config\Options $options): void | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
|
||
/** | ||
* This file is part of CaptainHook | ||
* | ||
* (c) Sebastian Feldmann <sf@sebastian-feldmann.info> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace CaptainHook\App\Plugin\Hook; | ||
|
||
use CaptainHook\App\Config; | ||
use CaptainHook\App\Plugin; | ||
use CaptainHook\App\Plugin\Hook\Exception\ActionChangedFiles; | ||
use CaptainHook\App\Runner\Hook as RunnerHook; | ||
use SebastianFeldmann\Git\Diff\File; | ||
|
||
/** | ||
* DisallowActionChanges runner plugin | ||
* | ||
* @package CaptainHook | ||
* @author Sebastian Feldmann <sf@sebastian-feldmann.info> | ||
* @link https://github.com/captainhookphp/captainhook | ||
* @since Class available since Release 5.11.0. | ||
*/ | ||
class DisallowActionChanges extends PreserveWorkingTree implements Plugin\Hook | ||
{ | ||
/** | ||
* @var iterable | ||
*/ | ||
private $priorDiff = []; | ||
|
||
/** | ||
* An array of actions that made changes to files. Each action name is the | ||
* key for an array of file changes made by that action. | ||
* | ||
* @var array<string, File[]> | ||
*/ | ||
private $actionChanges = []; | ||
|
||
public function beforeHook(RunnerHook $hook): void | ||
{ | ||
parent::beforeHook($hook); | ||
|
||
// Get a diff of the current state of the working tree. Since we ran | ||
// the parent beforeHook(), which moves changes out of the working | ||
// tree, this should be an empty diff. | ||
$this->priorDiff = $this->repository->getDiffOperator()->compareTo(); | ||
} | ||
|
||
public function afterAction(RunnerHook $hook, Config\Action $action): void | ||
{ | ||
$afterDiff = $this->repository->getDiffOperator()->compareTo(); | ||
|
||
// Did this action make any changes? | ||
if ($afterDiff != $this->priorDiff) { | ||
$this->actionChanges[$action->getAction()] = $afterDiff; | ||
} | ||
|
||
$this->priorDiff = $afterDiff; | ||
} | ||
|
||
public function afterHook(RunnerHook $hook): void | ||
{ | ||
parent::afterHook($hook); | ||
|
||
if (count($this->actionChanges) > 0) { | ||
$message = ''; | ||
foreach ($this->actionChanges as $action => $changes) { | ||
$message .= '<error>Action \'' . $action | ||
. '\' on hook ' . $hook->getName() | ||
. ' modified files</error>' | ||
. PHP_EOL; | ||
} | ||
|
||
throw new ActionChangedFiles($message); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace CaptainHook\App\Plugin\Hook; | ||
|
||
use CaptainHook\App\Config; | ||
use CaptainHook\App\Plugin; | ||
use CaptainHook\App\Runner\Hook as RunnerHook; | ||
|
||
class Example extends Base implements Plugin\Hook | ||
{ | ||
/** | ||
* Runs before the hook. | ||
* | ||
* @param RunnerHook $hook This is the current hook that's running. | ||
*/ | ||
public function beforeHook(RunnerHook $hook): void | ||
{ | ||
$this->io->write(['<fg=magenta>Plugin ' . self::class . '::beforeHook()</>', '']); | ||
} | ||
|
||
/** | ||
* Runs before each action. | ||
* | ||
* @param RunnerHook $hook This is the current hook that's running. | ||
* @param Config\Action $action This is the configuration for action that will | ||
* run immediately following this method. | ||
*/ | ||
public function beforeAction(RunnerHook $hook, Config\Action $action): void | ||
{ | ||
$this->io->write(['', ' <fg=cyan>Plugin ' . self::class . '::beforeAction()</>']); | ||
} | ||
|
||
/** | ||
* Runs after each action. | ||
* | ||
* @param RunnerHook $hook This is the current hook that's running. | ||
* @param Config\Action $action This is the configuration for action that just | ||
* ran immediately before this method. | ||
*/ | ||
public function afterAction(RunnerHook $hook, Config\Action $action): void | ||
{ | ||
$this->io->write(['', ' <fg=cyan>Plugin ' . self::class . '::afterAction()</>', '']); | ||
} | ||
|
||
/** | ||
* Runs after the hook. | ||
* | ||
* @param RunnerHook $hook This is the current hook that's running. | ||
*/ | ||
public function afterHook(RunnerHook $hook): void | ||
{ | ||
$this->io->write(['', '<fg=magenta>Plugin ' . self::class . '::afterHook()</>']); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
/** | ||
* This file is part of CaptainHook | ||
* | ||
* (c) Sebastian Feldmann <sf@sebastian-feldmann.info> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace CaptainHook\App\Plugin\Hook\Exception; | ||
|
||
use CaptainHook\App\Exception\CaptainHookException; | ||
use RuntimeException; | ||
|
||
/** | ||
* Class ActionChangedFiles | ||
* | ||
* @package CaptainHook | ||
* @author Sebastian Feldmann <sf@sebastian-feldmann.info> | ||
* @link https://github.com/captainhookphp/captainhook | ||
* @since Class available since Release 5.11.0. | ||
*/ | ||
class ActionChangedFiles extends RuntimeException implements CaptainHookException | ||
{ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about creating three separate commands (e.g.
hook:pre-commit:list-actions
) instead of adding those as options with need for this magic?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of separating the list functionality from the execution functionality
I think one command is enough
captainhook list-actions pre-commit
:)Then this can be done in a separate
Runner\ListActions
Maybe we can move the
getHookConfigsToHandle
functionality to theConfig
class. This way we would not have duplicate code inRunner\Hook
andRunner\ListActions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ramsey have you seen this comment as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds good to me.