Skip to content

Commit 236fe14

Browse files
Merge pull request #224 from feature/uninstall
Add uninstall command to the Cap'n
2 parents f16c4a0 + 1110656 commit 236fe14

File tree

11 files changed

+680
-157
lines changed

11 files changed

+680
-157
lines changed

src/Config/Options.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ public function __construct(array $options)
4141
/**
4242
* Return a option value
4343
*
44-
* @template T
45-
* @param string $name
46-
* @param T $default
47-
* @return T|null
44+
* @template ProvidedDefault
45+
* @param string $name
46+
* @param ProvidedDefault $default
47+
* @return ProvidedDefault|mixed
4848
*/
4949
public function get(string $name, $default = null)
5050
{
51-
return isset($this->options[$name]) ? $this->options[$name] : $default;
51+
return $this->options[$name] ?? $default;
5252
}
5353

5454
/**

src/Console/Command/Uninstall.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CaptainHook
5+
*
6+
* (c) Sebastian Feldmann <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace CaptainHook\App\Console\Command;
13+
14+
use CaptainHook\App\Console\IOUtil;
15+
use CaptainHook\App\Runner\Uninstaller;
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Input\InputOption;
19+
use Symfony\Component\Console\Output\OutputInterface;
20+
21+
/**
22+
* Class Uninstall
23+
*
24+
* @package CaptainHook
25+
* @author Sebastian Feldmann <[email protected]>
26+
* @link https://github.com/captainhookphp/captainhook
27+
* @since Class available since Release 5.17.0
28+
*/
29+
class Uninstall extends RepositoryAware
30+
{
31+
/**
32+
* Configure the command
33+
*
34+
* @return void
35+
*/
36+
protected function configure(): void
37+
{
38+
parent::configure();
39+
$this->setName('uninstall')
40+
->setDescription('Uninstall git hooks')
41+
->setHelp('This command will remove the git hooks from your .git directory')
42+
->addArgument(
43+
'hook',
44+
InputArgument::OPTIONAL,
45+
'Limit the hook you want to uninstall. By default all hooks get uninstalled.'
46+
)
47+
->addOption(
48+
'move-existing-to',
49+
null,
50+
InputOption::VALUE_OPTIONAL,
51+
'Move existing hooks to given directory'
52+
);
53+
}
54+
55+
/**
56+
* Execute the command
57+
*
58+
* @param \Symfony\Component\Console\Input\InputInterface $input
59+
* @param \Symfony\Component\Console\Output\OutputInterface $output
60+
* @return int
61+
* @throws \Exception
62+
*/
63+
protected function execute(InputInterface $input, OutputInterface $output)
64+
{
65+
$io = $this->getIO($input, $output);
66+
$config = $this->createConfig($input, true, ['git-directory']);
67+
$repo = $this->createRepository(dirname($config->getGitDirectory()));
68+
69+
// use the configured verbosity to manage general output verbosity
70+
$output->setVerbosity(IOUtil::mapConfigVerbosity($config->getVerbosity()));
71+
72+
$uninstaller = new Uninstaller($io, $config, $repo);
73+
$uninstaller->setMoveExistingTo(IOUtil::argToString($input->getOption('move-existing-to')))
74+
->setHook(IOUtil::argToString($input->getArgument('hook')))
75+
->run();
76+
return 0;
77+
}
78+
}

src/Hook/PHP/Action/TestCoverage.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,21 @@ class TestCoverage implements Action
3535
*
3636
* @var string
3737
*/
38-
private $cloverXmlFile;
38+
private string $cloverXmlFile;
3939

4040
/**
4141
* Path to PHPUnit
4242
*
4343
* @var string
4444
*/
45-
private $phpUnit;
45+
private string $phpUnit;
4646

4747
/**
4848
* Minimum coverage in percent
4949
*
5050
* @var int
5151
*/
52-
private $minCoverage;
52+
private int $minCoverage;
5353

5454
/**
5555
* Executes the action.

src/Runner/Files.php

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CaptainHook
5+
*
6+
* (c) Sebastian Feldmann <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace CaptainHook\App\Runner;
15+
16+
use CaptainHook\App\Exception;
17+
use CaptainHook\App\Hook\Util as HookUtil;
18+
use CaptainHook\App\Hooks;
19+
use RuntimeException;
20+
use SebastianFeldmann\Camino\Check;
21+
22+
/**
23+
* Class HookMover
24+
*
25+
* @package CaptainHook
26+
* @author Sebastian Feldmann <[email protected]>
27+
* @link https://github.com/captainhookphp/captainhook
28+
* @since Class available since Release 5.11.0
29+
*/
30+
abstract class Files extends RepositoryAware
31+
{
32+
/**
33+
* Install hooks brute force
34+
*
35+
* @var bool
36+
*/
37+
protected bool $force = false;
38+
39+
/**
40+
* Path where the existing hooks should be moved to
41+
*
42+
* @var string
43+
*/
44+
protected string $moveExistingTo = '';
45+
46+
/**
47+
* Hook that should be handled.
48+
*
49+
* @var array<int, string>
50+
*/
51+
protected array $hooksToHandle;
52+
53+
/**
54+
* @param bool $force
55+
* @return static
56+
*/
57+
public function setForce(bool $force): self
58+
{
59+
$this->force = $force;
60+
return $this;
61+
}
62+
63+
/**
64+
* Set the path where the current hooks should be moved to
65+
*
66+
* @param string $backup
67+
* @return static
68+
*/
69+
public function setMoveExistingTo(string $backup): self
70+
{
71+
$this->moveExistingTo = $backup;
72+
return $this;
73+
}
74+
75+
/**
76+
* Limit uninstall to s specific hook
77+
*
78+
* @param string $hook
79+
* @return static
80+
* @throws \CaptainHook\App\Exception\InvalidHookName
81+
*/
82+
public function setHook(string $hook): self
83+
{
84+
if (empty($hook)) {
85+
return $this;
86+
}
87+
88+
/** @var array<string> $hooks */
89+
$hooks = explode(',', $hook);
90+
$hooks = array_map('trim', $hooks);
91+
92+
$hooksValidationCallback = static function (string $hook): bool {
93+
return !HookUtil::isInstallable($hook);
94+
};
95+
if (!empty(($invalidHooks = array_filter($hooks, $hooksValidationCallback)))) {
96+
throw new Exception\InvalidHookName(
97+
'Invalid hook name \'' . implode('\', \'', $invalidHooks) . '\''
98+
);
99+
}
100+
101+
$this->hooksToHandle = $hooks;
102+
103+
return $this;
104+
}
105+
106+
/**
107+
* Return list of hooks to handle
108+
*
109+
* [
110+
* string => bool
111+
* HOOK_NAME => ASK_USER_TO_CONFIRM_INSTALL
112+
* ]
113+
*
114+
* @return array<string, bool>
115+
*/
116+
protected function getHooksToHandle(): array
117+
{
118+
// if specific hooks are set, the user has actively chosen it, so don't ask for permission anymore
119+
// if all hooks get installed ask for permission
120+
return !empty($this->hooksToHandle)
121+
? array_map(fn($hook) => false, array_flip($this->hooksToHandle))
122+
: array_map(fn($hook) => true, Hooks::nativeHooks());
123+
}
124+
125+
/**
126+
* If a path to incorporate the existing hook is set we should incorporate existing hooks
127+
*
128+
* @return bool
129+
*/
130+
protected function shouldHookBeMoved(): bool
131+
{
132+
return !empty($this->moveExistingTo);
133+
}
134+
135+
/**
136+
* Move the existing hook to the configured location
137+
*
138+
* @param string $hook
139+
*/
140+
protected function backupHook(string $hook): void
141+
{
142+
// no hook to move just exit
143+
if (!$this->repository->hookExists($hook)) {
144+
return;
145+
}
146+
147+
$hookFileOrig = $this->repository->getHooksDir() . DIRECTORY_SEPARATOR . $hook;
148+
$hookCmd = rtrim($this->moveExistingTo, '/\\') . DIRECTORY_SEPARATOR . $hook;
149+
$hookCmdArgs = $hookCmd . Hooks::getOriginalHookArguments($hook);
150+
$hookFileTarget = !Check::isAbsolutePath($this->moveExistingTo)
151+
? dirname($this->config->getPath()) . DIRECTORY_SEPARATOR . $hookCmd
152+
: $hookCmd;
153+
154+
$this->moveExistingHook($hookFileOrig, $hookFileTarget);
155+
156+
$this->io->write(
157+
[
158+
' Moved existing ' . $hook . ' hook to ' . $hookCmd,
159+
' Add <comment>\'' . $hookCmdArgs . '\'</comment> to your '
160+
. $hook . ' configuration to execute it.'
161+
]
162+
);
163+
}
164+
165+
/**
166+
* If the hook exists the user has to confirm the action
167+
*
168+
* @param string $hook The name of the hook to check
169+
* @return bool
170+
*/
171+
protected function needConfirmation(string $hook): bool
172+
{
173+
return $this->repository->hookExists($hook) && !$this->force;
174+
}
175+
176+
/**
177+
* Move the existing hook script to the new location
178+
*
179+
* @param string $originalLocation
180+
* @param string $newLocation
181+
* @return void
182+
* @throws \RuntimeException
183+
*/
184+
protected function moveExistingHook(string $originalLocation, string $newLocation): void
185+
{
186+
$dir = dirname($newLocation);
187+
// make sure the target directory isn't a file
188+
if (file_exists($dir) && !is_dir($dir)) {
189+
throw new RuntimeException($dir . ' is not a directory');
190+
}
191+
// create the directory if it does not exist
192+
if (!is_dir($dir)) {
193+
mkdir($dir, 0755, true);
194+
}
195+
// move the hook into the target directory
196+
rename($originalLocation, $newLocation);
197+
}
198+
}

src/Runner/Hook/PrepareCommitMsg.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ class PrepareCommitMsg extends Hook
3636
protected $hook = Hooks::PREPARE_COMMIT_MSG;
3737

3838
/**
39-
* @var string|null
39+
* @var string
4040
*/
41-
private ?string $commentChar;
41+
private string $commentChar;
4242

4343
/**
4444
* Path to commit message file

0 commit comments

Comments
 (0)