Skip to content

Commit 532d8d7

Browse files
Merge pull request #207 from CircleCode
add --only-installed installer option (closes #174)
2 parents 935d8f4 + 35dad0e commit 532d8d7

18 files changed

+303
-26
lines changed

src/Config.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,30 @@ public function isFailureAllowed(): bool
158158
return (bool) ($this->settings[self::SETTING_ALLOW_FAILURE] ?? false);
159159
}
160160

161+
/**
162+
* @param string $hook
163+
* @param bool $withVirtual if true, also check if hook is enabled through any enabled virtual hook
164+
* @return bool
165+
*/
166+
public function isHookEnabled(string $hook, bool $withVirtual = true): bool
167+
{
168+
//Either this hook is explicitely enabled
169+
$hookConfig = $this->getHookConfig($hook);
170+
if ($hookConfig->isEnabled()) {
171+
return true;
172+
}
173+
174+
//Or any virtual hook that triggers it is enabled
175+
if ($withVirtual && Hooks::triggersVirtualHook($hookConfig->getName())) {
176+
$virtualHookConfig = $this->getHookConfig(Hooks::getVirtualHook($hookConfig->getName()));
177+
if ($virtualHookConfig->isEnabled()) {
178+
return true;
179+
}
180+
}
181+
182+
return false;
183+
}
184+
161185
/**
162186
* Path getter
163187
*

src/Console/Command/Install.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ protected function configure(): void
5050
'You can specify multiple hooks with comma as delimiter. ' .
5151
'By default all hooks get installed.'
5252
)
53+
->addOption(
54+
'only-enabled',
55+
null,
56+
InputOption::VALUE_NONE,
57+
'Limit the hooks you want to install to those enabled in your conf. By default all hooks get installed.'
58+
)
5359
->addOption(
5460
'force',
5561
'f',
@@ -122,6 +128,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
122128
$installer->setForce(IOUtil::argToBool($input->getOption('force')))
123129
->setSkipExisting(IOUtil::argToBool($input->getOption('skip-existing')))
124130
->setMoveExistingTo(IOUtil::argToString($input->getOption('move-existing-to')))
131+
->setOnlyEnabled(IOUtil::argToBool($input->getOption('only-enabled')))
125132
->setHook(IOUtil::argToString($input->getArgument('hook')))
126133
->run();
127134

src/Runner/Hook.php

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,7 @@ public function run(): void
137137

138138
$this->io->write('<comment>' . $this->hook . ':</comment> ');
139139

140-
// if the hook and all triggered virtual hooks
141-
// are NOT enabled in the captainhook configuration skip the execution
142-
if (!$this->isAnyConfigEnabled($hookConfigs)) {
140+
if(!$this->config->isHookEnabled($this->hook)) {
143141
$this->io->write(' - hook is disabled');
144142
return;
145143
}
@@ -186,20 +184,6 @@ public function getHookConfigsToHandle(): array
186184
return $configs;
187185
}
188186

189-
/**
190-
* @param \CaptainHook\App\Config\Hook[] $configs
191-
* @return bool
192-
*/
193-
private function isAnyConfigEnabled(array $configs): bool
194-
{
195-
foreach ($configs as $hookConfig) {
196-
if ($hookConfig->isEnabled()) {
197-
return true;
198-
}
199-
}
200-
return false;
201-
}
202-
203187
/**
204188
* Returns `true` if something has indicated that the hook should skip all
205189
* remaining actions; pass a boolean value to toggle this

src/Runner/Installer.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ class Installer extends RepositoryAware
5757
private $moveExistingTo = '';
5858

5959
/**
60-
* Hooks that should be handled.
60+
* Install only enabled hooks
61+
*
62+
* @var bool
63+
*/
64+
private $onlyEnabled = false;
65+
66+
/**
6167
*
6268
* @var array<int, string>
6369
*/
@@ -129,6 +135,20 @@ public function setMoveExistingTo(string $backup): Installer
129135
return $this;
130136
}
131137

138+
/**
139+
* @param bool $onlyEnabled
140+
* @return \CaptainHook\App\Runner\Installer
141+
*/
142+
public function setOnlyEnabled(bool $onlyEnabled): Installer
143+
{
144+
if ($onlyEnabled && !empty($this->hooksToHandle)) {
145+
throw new RuntimeException('choose --only-enabled or specific hooks');
146+
}
147+
148+
$this->onlyEnabled = $onlyEnabled;
149+
return $this;
150+
}
151+
132152
/**
133153
* Hook setter
134154
*
@@ -142,6 +162,10 @@ public function setHook(string $hook): Installer
142162
return $this;
143163
}
144164

165+
if ($this->onlyEnabled) {
166+
throw new RuntimeException('choose --only-enabled or specific hooks');
167+
}
168+
145169
/** @var array<string> $hooks */
146170
$hooks = explode(',', $hook);
147171
$hooks = array_map('trim', $hooks);
@@ -189,9 +213,18 @@ public function getHooksToInstall(): array
189213
// to make sure the user will be asked to confirm every hook installation
190214
// unless the user provided the force or skip option
191215
// if specific hooks are set, the use has actively chosen it, so don't ask for permission anymore
192-
return empty($this->hooksToHandle)
193-
? array_map(fn ($hook) => true, Hooks::nativeHooks())
194-
: array_map(fn ($hook) => false, array_flip($this->hooksToHandle));
216+
if (!empty($this->hooksToHandle)) {
217+
return array_map(fn($hook) => false, array_flip($this->hooksToHandle));
218+
}
219+
$hooks = Hooks::nativeHooks();
220+
if($this->onlyEnabled) {
221+
$hooks = array_filter(
222+
$hooks,
223+
fn($hook) => $this->config->isHookEnabled($hook),
224+
ARRAY_FILTER_USE_KEY
225+
);
226+
}
227+
return array_map(fn($hook) => true, $hooks);
195228
}
196229

197230
/**
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"post-change": {
3+
"enabled": true,
4+
"actions": []
5+
},
6+
"post-merge": {
7+
"enabled": false,
8+
"actions": []
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"post-change": {
3+
"enabled": true,
4+
"actions": []
5+
},
6+
"post-merge": {
7+
"enabled": true,
8+
"actions": []
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"post-change": {
3+
"enabled": true,
4+
"actions": []
5+
},
6+
"pre-commit": {
7+
"enabled": true,
8+
"actions": []
9+
}
10+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"post-change": {
3+
"enabled": true,
4+
"actions": []
5+
}
6+
}

tests/unit/Console/Command/InstallTest.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,171 @@ public function testInstallMultipleHooksWithMultipleWrong(): void
209209
$install = new Install(new Resolver(CH_PATH_FILES . '/bin/captainhook'));
210210
$install->run($input, $output);
211211
}
212+
213+
/**
214+
* Tests Install::run
215+
*
216+
* @throws \Exception
217+
*/
218+
public function testInstallOnlyEnabled(): void
219+
{
220+
$repo = new DummyRepo();
221+
$output = new NullOutput();
222+
$input = new ArrayInput(
223+
[
224+
'--only-enabled' => true,
225+
'--force' => true,
226+
'--configuration' => CH_PATH_FILES . '/template/captainhook.json',
227+
'--git-directory' => $repo->getGitDir()
228+
]
229+
);
230+
231+
$install = new Install(new Resolver(CH_PATH_FILES . '/bin/captainhook'));
232+
$install->run($input, $output);
233+
234+
$this->assertTrue($repo->hookExists('prepare-commit-msg'));
235+
$this->assertTrue($repo->hookExists('commit-msg'));
236+
$this->assertTrue($repo->hookExists('pre-commit'));
237+
$this->assertFalse($repo->hookExists('pre-push'));
238+
$this->assertFalse($repo->hookExists('post-commit'));
239+
}
240+
241+
/**
242+
* Tests Install::run
243+
*
244+
* @throws \Exception
245+
*/
246+
public function testInstallOnlyEnabledOnlyVirtual(): void
247+
{
248+
$repo = new DummyRepo();
249+
$output = new NullOutput();
250+
$input = new ArrayInput(
251+
[
252+
'--only-enabled' => true,
253+
'--force' => true,
254+
'--configuration' => CH_PATH_FILES . '/template/captainhook-post-change.json',
255+
'--git-directory' => $repo->getGitDir()
256+
]
257+
);
258+
259+
$install = new Install(new Resolver(CH_PATH_FILES . '/bin/captainhook'));
260+
$install->run($input, $output);
261+
262+
$this->assertTrue($repo->hookExists('post-checkout'));
263+
$this->assertTrue($repo->hookExists('post-merge'));
264+
$this->assertTrue($repo->hookExists('post-rewrite'));
265+
$this->assertFalse($repo->hookExists('pre-commit'));
266+
$this->assertFalse($repo->hookExists('pre-push'));
267+
$this->assertFalse($repo->hookExists('post-commit'));
268+
}
269+
270+
/**
271+
* Tests Install::run
272+
*
273+
* @throws \Exception
274+
*/
275+
public function testInstallOnlyEnabledNotOnlyVirtual(): void
276+
{
277+
$repo = new DummyRepo();
278+
$output = new NullOutput();
279+
$input = new ArrayInput(
280+
[
281+
'--only-enabled' => true,
282+
'--force' => true,
283+
'--configuration' => CH_PATH_FILES . '/template/captainhook-post-change-pre-commit.json',
284+
'--git-directory' => $repo->getGitDir()
285+
]
286+
);
287+
288+
$install = new Install(new Resolver(CH_PATH_FILES . '/bin/captainhook'));
289+
$install->run($input, $output);
290+
291+
$this->assertTrue($repo->hookExists('post-checkout'));
292+
$this->assertTrue($repo->hookExists('post-merge'));
293+
$this->assertTrue($repo->hookExists('post-rewrite'));
294+
$this->assertTrue($repo->hookExists('pre-commit'));
295+
$this->assertFalse($repo->hookExists('pre-push'));
296+
$this->assertFalse($repo->hookExists('post-commit'));
297+
}
298+
299+
/**
300+
* Tests Install::run
301+
*
302+
* @throws \Exception
303+
*/
304+
public function testInstallOnlyEnabledNotOnlyVirtualOverlaps(): void
305+
{
306+
$repo = new DummyRepo();
307+
$output = new NullOutput();
308+
$input = new ArrayInput(
309+
[
310+
'--only-enabled' => true,
311+
'--force' => true,
312+
'--configuration' => CH_PATH_FILES . '/template/captainhook-post-change-post-merge.json',
313+
'--git-directory' => $repo->getGitDir()
314+
]
315+
);
316+
317+
$install = new Install(new Resolver(CH_PATH_FILES . '/bin/captainhook'));
318+
$install->run($input, $output);
319+
320+
$this->assertTrue($repo->hookExists('post-checkout'));
321+
$this->assertTrue($repo->hookExists('post-merge'));
322+
$this->assertTrue($repo->hookExists('post-rewrite'));
323+
$this->assertFalse($repo->hookExists('pre-commit'));
324+
$this->assertFalse($repo->hookExists('pre-push'));
325+
$this->assertFalse($repo->hookExists('post-commit'));
326+
}
327+
328+
/**
329+
* Tests Install::run
330+
*
331+
* @throws \Exception
332+
*/
333+
public function testInstallOnlyEnabledNotOnlyVirtualOverlapsDisabled(): void
334+
{
335+
$repo = new DummyRepo();
336+
$output = new NullOutput();
337+
$input = new ArrayInput(
338+
[
339+
'--only-enabled' => true,
340+
'--force' => true,
341+
'--configuration' => CH_PATH_FILES . '/template/captainhook-post-change-post-merge-disabled.json',
342+
'--git-directory' => $repo->getGitDir()
343+
]
344+
);
345+
346+
$install = new Install(new Resolver(CH_PATH_FILES . '/bin/captainhook'));
347+
$install->run($input, $output);
348+
349+
$this->assertTrue($repo->hookExists('post-checkout'));
350+
$this->assertTrue($repo->hookExists('post-merge'));
351+
$this->assertTrue($repo->hookExists('post-rewrite'));
352+
$this->assertFalse($repo->hookExists('pre-commit'));
353+
$this->assertFalse($repo->hookExists('pre-push'));
354+
$this->assertFalse($repo->hookExists('post-commit'));
355+
}
356+
357+
/**
358+
* Tests Install::run
359+
*
360+
* @throws \Exception
361+
*/
362+
public function testInstallOnlyEnabledAndHook(): void
363+
{
364+
$this->expectException(\RuntimeException::class);
365+
$repo = new DummyRepo();
366+
$output = new NullOutput();
367+
$input = new ArrayInput(
368+
[
369+
'hook' => 'pre-commit',
370+
'--only-enabled' => true,
371+
'--configuration' => CH_PATH_FILES . '/template/captainhook.json',
372+
'--git-directory' => $repo->getGitDir(),
373+
]
374+
);
375+
376+
$install = new Install(new Resolver(CH_PATH_FILES . '/bin/captainhook'));
377+
$install->run($input, $output);
378+
}
212379
}

tests/unit/Runner/Hook/CommitMsgTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function testRunHookEnabled(): void
4848
$hookConfig->expects($this->atLeast(1))->method('isEnabled')->willReturn(true);
4949
$hookConfig->expects($this->once())->method('getActions')->willReturn([$actionConfig]);
5050
$config->expects($this->once())->method('getHookConfig')->willReturn($hookConfig);
51+
$config->expects($this->atLeastOnce())->method('isHookEnabled')->willReturn(true);
5152
$io->expects($this->atLeast(1))->method('write');
5253
$io->expects($this->once())->method('getArgument')->willReturn(CH_PATH_FILES . '/git/message/valid.txt');
5354

@@ -79,6 +80,7 @@ public function testRunHookSkippedBecauseOfFixup(): void
7980
$hookConfig->method('isEnabled')->willReturn(true);
8081
$hookConfig->method('getActions')->willReturn([$actionConfig]);
8182
$config->method('getHookConfig')->willReturn($hookConfig);
83+
$config->expects($this->atLeastOnce())->method('isHookEnabled')->willReturn(true);
8284
$io->expects($this->atLeast(1))->method('write');
8385
$io->expects($this->once())->method('getArgument')->willReturn(CH_PATH_FILES . '/git/message/valid.txt');
8486

@@ -105,6 +107,7 @@ public function testRunWithoutCommitMsgFile(): void
105107
$hookConfig->method('isEnabled')->willReturn(true);
106108
$hookConfig->method('getActions')->willReturn([$actionConfig]);
107109
$config->expects($this->once())->method('getHookConfig')->willReturn($hookConfig);
110+
$config->expects($this->atLeastOnce())->method('isHookEnabled')->willReturn(true);
108111
$io->expects($this->once())->method('getArgument')->willReturn('');
109112

110113
$runner = new CommitMsg($io, $config, $repo);

0 commit comments

Comments
 (0)