Skip to content

Commit 48cb2de

Browse files
authored
Merge branch 'fluffy_hedgehog' into updateToComposerPluginApi2
2 parents f035847 + 3e122bc commit 48cb2de

File tree

3 files changed

+219
-35
lines changed

3 files changed

+219
-35
lines changed

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Composer-Plugin for [CaptainHook](https://github.com/captainhookphp/captainhook)
22

3-
This is a composer-plugin that installs _CaptainHook_ and the corresponding git hooks. For more information visit its [Website](https://github.com/captainhookphp/captainhook).
3+
This is a composer-plugin that makes sure your team mates install the git hooks. For more information visit its [Website](https://github.com/captainhookphp/captainhook).
44

55
[![Latest Stable Version](https://poser.pugx.org/captainhook/plugin-composer/v/stable.svg?v=1)](https://packagist.org/packages/captainhook/plugin-composer)
66
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)](https://php.net/)
@@ -19,24 +19,24 @@ Everything else will happen automagically.
1919

2020
## Customize
2121

22-
You can set a custom name for your hook configuration and a custom path to your .git directory
23-
if it is not located in the same directory as your *composer.json* file.
24-
Just add these values to your *composer.json* in the *extra* section first.
22+
You can set a custom name for your hook configuration.
23+
If you want to use the PHAR release of `CaptainHook` you can configure the path to the PHAR file.
24+
All extra config settings are optional and if you are using the default settings you do not have to
25+
configure anything to make it work.
26+
2527
```json
2628
{
2729
"extra": {
28-
"captainhook-config": "hooks.json",
29-
"captainhook-git-dir": "../.git"
30-
}
31-
30+
"captainhook": {
31+
"config": "hooks.json",
32+
"exec": "tools/captainhook.phar",
33+
"disable-plugin": false
34+
}
35+
}
3236
}
3337

3438
```
3539

36-
If you want to see the installation in action have a look at this short installation video.
37-
38-
[![Install demo](http://img.youtube.com/vi/agwTZ0jhDDs/0.jpg)](https://www.youtube.com/watch?v=agwTZ0jhDDs)
39-
4040
## A word of warning
4141

4242
It is still possible to commit without invoking the hooks.

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"require": {
1717
"php": "^7.1",
1818
"composer-plugin-api": "^1.1|^2.0",
19-
"org_heigl/trait-iterator": "^1.1",
2019
"captainhook/captainhook": "^5.0"
2120
},
2221
"require-dev": {

src/ComposerPlugin.php

Lines changed: 207 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111

1212
namespace CaptainHook\Plugin\Composer;
1313

14-
use CaptainHook\App\Composer\Cmd;
1514
use Composer\Composer;
1615
use Composer\EventDispatcher\EventSubscriberInterface;
1716
use Composer\IO\IOInterface;
1817
use Composer\Plugin\PluginInterface;
1918
use Composer\Script\Event;
2019
use Composer\Script\ScriptEvents;
20+
use RuntimeException;
2121

2222
/**
2323
* Class ComposerPlugin
@@ -28,16 +28,55 @@
2828
*/
2929
class ComposerPlugin implements PluginInterface, EventSubscriberInterface
3030
{
31+
private const COMMAND_CONFIGURE = 'configure';
32+
private const COMMAND_INSTALL = 'install';
33+
34+
/**
35+
* Composer instance
36+
*
37+
* @var \Composer\Composer
38+
*/
39+
private $composer;
40+
41+
/**
42+
* Composer IO instance
43+
*
44+
* @var \Composer\IO\IOInterface
45+
*/
46+
private $io;
47+
48+
/**
49+
* Path to the captainhook executable
50+
*
51+
* @var string
52+
*/
53+
private $executable;
54+
55+
/**
56+
* Path to the captainhook configuration file
57+
*
58+
* @var string
59+
*/
60+
private $configuration;
61+
62+
/**
63+
* Path to the .git directory
64+
*
65+
* @var string
66+
*/
67+
private $gitDirectory;
68+
3169
/**
3270
* Activate the plugin
3371
*
3472
* @param \Composer\Composer $composer
3573
* @param \Composer\IO\IOInterface $io
3674
* @return void
3775
*/
38-
public function activate(Composer $composer, IOInterface $io) : void
76+
public function activate(Composer $composer, IOInterface $io): void
3977
{
40-
// nothing to do here
78+
$this->composer = $composer;
79+
$this->io = $io;
4180
}
4281

4382
/**
@@ -75,10 +114,11 @@ public function uninstall(Composer $composer, IOInterface $io)
75114
*
76115
* @return array
77116
*/
78-
public static function getSubscribedEvents() : array
117+
public static function getSubscribedEvents(): array
79118
{
80119
return [
81-
ScriptEvents::POST_AUTOLOAD_DUMP => 'installHooks'
120+
ScriptEvents::POST_INSTALL_CMD => 'installHooks',
121+
ScriptEvents::POST_UPDATE_CMD => 'installHooks'
82122
];
83123
}
84124

@@ -89,39 +129,184 @@ public static function getSubscribedEvents() : array
89129
* @return void
90130
* @throws \Exception
91131
*/
92-
public function installHooks(Event $event) : void
132+
public function installHooks(Event $event): void
93133
{
94-
$event->getIO()->write('CaptainHook Composer Plugin');
95-
if (!$this->isCaptainHookInstalled()) {
96-
// reload the autoloader to make sure CaptainHook is available
97-
$vendorDir = $event->getComposer()->getConfig()->get('vendor-dir');
98-
require $vendorDir . '/autoload.php';
134+
$this->io->write('<info>CaptainHook Composer Plugin</info>');
135+
136+
if ($this->isPluginDisabled()) {
137+
$this->io->write(' <comment>plugin is disabled</comment>');
138+
return;
99139
}
100140

101-
if (!$this->isCaptainHookInstalled()) {
102-
// if CaptainHook is still not available end the plugin execution
103-
// normally this only happens if CaptainHook gets uninstalled
104-
$event->getIO()->write(
105-
' <info>CaptainHook not properly installed try to run composer update</info>' . PHP_EOL .
141+
$this->detectConfiguration();
142+
$this->detectGitDir();
143+
$this->detectCaptainExecutable();
144+
145+
if (!file_exists($this->executable)) {
146+
$this->io->write(
147+
'<comment>CaptainHook executable not found</comment>' . PHP_EOL .
106148
PHP_EOL .
149+
'Make sure you have installed the captainhook/captainhook package.' . PHP_EOL .
150+
'If you are using the PHAR you have to configure the path to your CaptainHook executable' . PHP_EOL .
151+
'using Composers \'extra\' config. e.g.' . PHP_EOL .
152+
PHP_EOL . '<comment>' .
153+
' "extra": {' . PHP_EOL .
154+
' "captainhook": {' . PHP_EOL .
155+
' "exec": "tools/captainhook.phar' . PHP_EOL .
156+
' }' . PHP_EOL .
157+
' }' . PHP_EOL .
158+
'</comment>' . PHP_EOL .
107159
'If you are uninstalling CaptainHook, we are sad seeing you go, ' .
108160
'but we would appreciate your feedback on your experience.' . PHP_EOL .
109161
'Just go to https://github.com/CaptainHookPhp/captainhook/issues to leave your feedback' . PHP_EOL .
110-
PHP_EOL .
111-
'<comment>WARNING: Don\'t forget to deactivate the hooks in your .git/hooks directory.</comment>'
162+
'<comment>WARNING: Don\'t forget to deactivate the hooks in your .git/hooks directory.</comment>' .
163+
PHP_EOL
112164
);
113165
return;
114166
}
115-
Cmd::setup($event);
167+
168+
$this->configure();
169+
$this->install();
170+
}
171+
172+
/**
173+
* Create captainhook.json file if it does not exist
174+
*/
175+
private function configure(): void
176+
{
177+
if (file_exists($this->configuration)) {
178+
$this->io->write((' <comment>Using CaptainHook config: ' . $this->configuration . '</comment>'));
179+
return;
180+
}
181+
182+
$this->runCaptainCommand(self::COMMAND_CONFIGURE);
183+
}
184+
185+
/**
186+
* Install hooks to your .git/hooks directory
187+
*/
188+
private function install(): void
189+
{
190+
$this->runCaptainCommand(self::COMMAND_INSTALL);
191+
}
192+
193+
/**
194+
* Executes CaptainHook in a sub process
195+
*
196+
* @param string $command
197+
*/
198+
private function runCaptainCommand(string $command): void
199+
{
200+
// Respect composer CLI settings
201+
$ansi = $this->io->isDecorated() ? ' --ansi' : ' --no-ansi';
202+
$interaction = $this->io->isInteractive() ? '' : ' --no-interaction';
203+
204+
// captainhook config and repository settings
205+
$configuration = ' -c ' . escapeshellarg($this->configuration);
206+
$repository = $command === self::COMMAND_INSTALL ? ' -g ' . escapeshellarg($this->gitDirectory) : '';
207+
$skip = $command === self::COMMAND_INSTALL ? ' -s' : '';
208+
$executable = str_replace(' ', '\\ ', $this->executable);
209+
210+
// sub process settings
211+
$cmd = $executable . ' ' . $command . $ansi . $interaction . $skip . $configuration . $repository;
212+
$pipes = [];
213+
$spec = [
214+
0 => ['file', 'php://stdin', 'r'],
215+
1 => ['file', 'php://stdout', 'w'],
216+
2 => ['file', 'php://stderr', 'w'],
217+
];
218+
219+
$process = @proc_open($cmd, $spec, $pipes);
220+
221+
if ($this->io->isVerbose()) {
222+
$this->io->write('Running process : ' . $cmd);
223+
}
224+
if (!is_resource($process)) {
225+
throw new RuntimeException($this->pluginErrorMessage('no-process'));
226+
}
227+
228+
// Loop on process until it exits normally.
229+
do {
230+
$status = proc_get_status($process);
231+
} while ($status && $status['running']);
232+
$exitCode = $status['exitcode'] ?? -1;
233+
proc_close($process);
234+
if ($exitCode !== 0) {
235+
$this->io->writeError($this->pluginErrorMessage('installation process failed'));
236+
}
237+
}
238+
239+
/**
240+
* Return path to the CaptainHook configuration file
241+
*
242+
* @return void
243+
*/
244+
private function detectConfiguration(): void
245+
{
246+
$extra = $this->composer->getPackage()->getExtra();
247+
$this->configuration = getcwd() . '/' . ($extra['captainhook']['config'] ?? 'captainhook.json');
248+
}
249+
250+
/**
251+
* Search for the git repository to store the hooks in
252+
253+
* @return void
254+
* @throws \RuntimeException
255+
*/
256+
private function detectGitDir(): void
257+
{
258+
$path = getcwd();
259+
260+
while (file_exists($path)) {
261+
$possibleGitDir = $path . '/.git';
262+
if (is_dir($possibleGitDir)) {
263+
$this->gitDirectory = $possibleGitDir;
264+
return;
265+
}
266+
267+
// if we checked the root directory already, break to prevent endless loop
268+
if ($path === dirname($path)) {
269+
break;
270+
}
271+
272+
$path = \dirname($path);
273+
}
274+
throw new RuntimeException($this->pluginErrorMessage('git directory not found'));
275+
}
276+
277+
/**
278+
* Creates a nice formatted error message
279+
*
280+
* @param string $reason
281+
* @return string
282+
*/
283+
private function pluginErrorMessage(string $reason): string
284+
{
285+
return 'Shiver me timbers! CaptainHook could not install yer git hooks! (' . $reason . ')';
286+
}
287+
288+
/**
289+
*
290+
*/
291+
private function detectCaptainExecutable(): void
292+
{
293+
$extra = $this->composer->getPackage()->getExtra();
294+
if (isset($extra['captainhook']['exec'])) {
295+
$this->executable = $extra['captainhook']['exec'];
296+
return;
297+
}
298+
299+
$this->executable = (string) $this->composer->getConfig()->get('bin-dir') . '/captainhook';
116300
}
117301

118302
/**
119-
* Checks if CaptainHook is installed properly
303+
* Check if the plugin is disabled
120304
*
121305
* @return bool
122306
*/
123-
private function isCaptainHookInstalled() : bool
307+
private function isPluginDisabled(): bool
124308
{
125-
return class_exists('\\CaptainHook\\App\\CH');
309+
$extra = $this->composer->getPackage()->getExtra();
310+
return (bool) ($extra['captainhook']['disable-plugin'] ?? false);
126311
}
127312
}

0 commit comments

Comments
 (0)