Skip to content

Commit

Permalink
Use the new boostrap utility
Browse files Browse the repository at this point in the history
Since the bootstrap logic is executed during hook execution and hook
installation the templates must be aware of the current runtime and
treat PHAR files a bit differently.

Fix issue #248
  • Loading branch information
sebastianfeldmann committed Jul 7, 2024
1 parent dc5d48a commit 59708d7
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 72 deletions.
15 changes: 6 additions & 9 deletions src/Console/Command/Hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use CaptainHook\App\Config;
use CaptainHook\App\Console\IOUtil;
use CaptainHook\App\Hook\Util;
use Exception;
use CaptainHook\App\Runner\Bootstrap\Util as BootstrapUtil;
use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand Down Expand Up @@ -116,14 +116,11 @@ private function handleBootstrap(Config $config): void
// Composer installs the bootstrapping is already done in the bin script
if ($this->resolver->isPharRelease()) {
// check the custom and default autoloader
$bootstrapFile = dirname($config->getPath()) . '/' . $config->getBootstrap();
if (!file_exists($bootstrapFile)) {
// since the phar is doing its own autoloading we don't need to do anything
// if the bootstrap file is not actively set
if (empty($config->getBootstrap(''))) {
return;
}
throw new RuntimeException('bootstrap file not found');
$bootstrapFile = BootstrapUtil::validateBootstrapPath($this->resolver->isPharRelease(), $config);
// since the phar has its own autoloader we don't need to do anything
// if the bootstrap file is not actively set
if (empty($bootstrapFile)) {
return;
}
// the bootstrap file exists so lets load it
try {
Expand Down
37 changes: 12 additions & 25 deletions src/Hook/Template/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use CaptainHook\App\Hook\Template\Local\PHP;
use CaptainHook\App\Hook\Template\Local\Shell;
use CaptainHook\App\Hook\Template\Local\WSL;
use CaptainHook\App\Runner\Bootstrap\Util;
use RuntimeException;
use SebastianFeldmann\Git\Repository;

Expand All @@ -44,37 +45,23 @@ abstract class Builder
*/
public static function build(Config $config, Repository $repository, Resolver $resolver): Template
{
$pathInfo = new PathInfo($repository->getRoot(), $config->getPath(), $resolver->getExecutable());
$bootstrapPath = dirname($config->getPath()) . '/' . $config->getBootstrap();

if (!file_exists($bootstrapPath)) {
throw new RuntimeException('bootstrap file not found: \'' . $bootstrapPath . '\'');
}
$pathInfo = new PathInfo(
$repository->getRoot(),
$config->getPath(),
$resolver->getExecutable(),
$resolver->isPharRelease()
);
Util::validateBootstrapPath($resolver->isPharRelease(), $config);

switch ($config->getRunConfig()->getMode()) {
case Template::DOCKER:
return new Docker(
$pathInfo,
$config
);
return new Docker($pathInfo, $config);
case Template::PHP:
return new PHP(
$pathInfo,
$config,
$resolver->isPharRelease()
);
return new PHP($pathInfo, $config);
case Template::WSL:
return new WSL(
$pathInfo,
$config,
$resolver->isPharRelease()
);
return new WSL($pathInfo, $config);
default:
return new Shell(
$pathInfo,
$config,
$resolver->isPharRelease()
);
return new Shell($pathInfo, $config);
}
}
}
3 changes: 2 additions & 1 deletion src/Hook/Template/Docker.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CaptainHook\App\Config;
use CaptainHook\App\Hook\Template;
use CaptainHook\App\Hooks;
use CaptainHook\App\Runner\Bootstrap\Util;

/**
* Docker class
Expand Down Expand Up @@ -67,7 +68,7 @@ public function getCode(string $hook): string
{
$path2Config = $this->pathInfo->getConfigPath();
$config = $path2Config !== CH::CONFIG ? ' --configuration=' . escapeshellarg($path2Config) : '';
$bootstrap = !empty($this->config->getBootstrap()) ? ' --bootstrap=' . $this->config->getBootstrap() : '';
$bootstrap = Util::bootstrapCmdOption($this->pathInfo->isPhar(), $this->config);

$lines = [
'#!/bin/sh',
Expand Down
22 changes: 12 additions & 10 deletions src/Hook/Template/Local.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use CaptainHook\App\Config;
use CaptainHook\App\Hook\Template;
use CaptainHook\App\Runner\Bootstrap\Util;

abstract class Local implements Template
{
Expand All @@ -32,25 +33,16 @@ abstract class Local implements Template
*/
protected Config $config;

/**
* Is the executable a phar file
*
* @var bool
*/
protected bool $isPhar;

/**
* Local constructor
*
* @param \CaptainHook\App\Hook\Template\PathInfo $pathInfo
* @param \CaptainHook\App\Config $config
* @param bool $isPhar
*/
public function __construct(PathInfo $pathInfo, Config $config, bool $isPhar)
public function __construct(PathInfo $pathInfo, Config $config)
{
$this->pathInfo = $pathInfo;
$this->config = $config;
$this->isPhar = $isPhar;
}

/**
Expand All @@ -64,6 +56,16 @@ public function getCode(string $hook): string
return implode(PHP_EOL, $this->getHookLines($hook)) . PHP_EOL;
}

/**
* Returns the bootstrap option depending on the current runtime (can be empty)
*
* @return string
*/
public function getBootstrapCmdOption(): string
{
return Util::bootstrapCmdOption($this->pathInfo->isPhar(), $this->config);
}

/**
* Return the code for the git hook scripts
*
Expand Down
9 changes: 6 additions & 3 deletions src/Hook/Template/Local/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class PHP extends Template\Local
*/
public function getHookLines(string $hook): array
{
return $this->isPhar ? $this->getPharHookLines($hook) : $this->getSrcHookLines($hook);
return $this->pathInfo->isPhar() ? $this->getPharHookLines($hook) : $this->getSrcHookLines($hook);
}

/**
Expand Down Expand Up @@ -105,12 +105,15 @@ private function getPharHookLines(string $hook): array
{
$configPath = $this->pathInfo->getConfigPath();
$executablePath = $this->pathInfo->getExecutablePath();
$bootstrap = $this->config->getBootstrap();
$stdIn = $this->getStdInHandling($hook);

$executableInclude = substr($executablePath, 0, 1) == '/'
? '\'' . $executablePath . '\''
: '__DIR__ . \'/../../' . $executablePath . '\'';

$bootstrapOption = $this->getBootstrapCmdOption();
$bootstrapOptionQuoted = empty($bootstrapOption) ? '' : ' \'' . $bootstrapOption . '\',';

return array_merge(
[
'#!/usr/bin/env php',
Expand All @@ -127,7 +130,7 @@ private function getPharHookLines(string $hook): array
' \'hook:' . $hook . '\',',
' \'--configuration=' . $configPath . ',',
' \'--git-directory=\' . dirname(__DIR__, 2) . \'/.git\',',
' \'--bootstrap=' . $bootstrap . '\',',
$bootstrapOptionQuoted,
' \'--input=\' . trim($input) . \'\',',
' ],',
' array_slice($argv, 1)',
Expand Down
2 changes: 1 addition & 1 deletion src/Hook/Template/Local/Shell.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected function getHookLines(string $hook): array
$this->getExecutable()
. ' $INTERACTIVE'
. ' --configuration=' . $this->pathInfo->getConfigPath()
. ' --bootstrap=' . $this->config->getBootstrap()
. $this->getBootstrapCmdOption()
. ' --input="$input"'
. ' hook:' . $hook . ' "$@"'
];
Expand Down
30 changes: 29 additions & 1 deletion src/Hook/Template/PathInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,35 @@ class PathInfo
*/
private File $executable;

/**
* PHAR or composer runtime
*
* @var bool
*/
private bool $isPhar;

/**
* @param string $repositoryPath
* @param string $configPath
* @param string $execPath
* @param bool $isPhar
*/
public function __construct(string $repositoryPath, string $configPath, string $execPath)
public function __construct(string $repositoryPath, string $configPath, string $execPath, bool $isPhar)
{
$this->repositoryAbsolute = self::toAbsolutePath($repositoryPath);
$this->repository = new Directory($this->repositoryAbsolute);
$this->configAbsolute = self::toAbsolutePath($configPath);
$this->config = new File($this->configAbsolute);
$this->executableAbsolute = self::toAbsolutePath($execPath);
$this->executable = new File($this->executableAbsolute);
$this->isPhar = $isPhar;
}

/**
* Returns the path to the captainhook executable
*
* @return string
*/
public function getExecutablePath(): string
{
// check if the captainhook binary is in the repository bin directory
Expand All @@ -76,11 +90,25 @@ public function getExecutablePath(): string
return $this->getPathFromTo($this->repository, $this->executable);
}

/**
* Returns the path to the captainhook configuration file
* @return string
*/
public function getConfigPath(): string
{
return $this->getPathFromTo($this->repository, $this->config);
}

/**
* Runtime indicator
*
* @return bool
*/
public function isPhar(): bool
{
return $this->isPhar;
}

/**
* Return the path to the target path from inside the .git/hooks directory f.e. __DIR__ ../../vendor
*
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/Console/Command/Hook/PreCommitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public function testExecuteSkip(): void
public function testExecutePhar(): void
{
$resolver = $this->createMock(Resolver::class);
$resolver->expects($this->once())->method('isPharRelease')->willReturn(true);
$resolver->expects($this->atLeast(1))->method('isPharRelease')->willReturn(true);

$repo = new DummyRepo();
$output = new NullOutput();
Expand All @@ -106,7 +106,7 @@ public function testExecutePhar(): void
public function testExecutePharBootstrapNotFound(): void
{
$resolver = $this->createMock(Resolver::class);
$resolver->expects($this->once())->method('isPharRelease')->willReturn(true);
$resolver->expects($this->atLeast(1))->method('isPharRelease')->willReturn(true);

$repo = new DummyRepo();
$output = new NullOutput();
Expand All @@ -128,7 +128,7 @@ public function testExecutePharBootstrapNotFound(): void
public function testExecutePharBootstrapNotSet(): void
{
$resolver = $this->createMock(Resolver::class);
$resolver->expects($this->once())->method('isPharRelease')->willReturn(true);
$resolver->expects($this->atLeast(1))->method('isPharRelease')->willReturn(true);

$repo = new DummyRepo();
$output = new NullOutput();
Expand Down
60 changes: 58 additions & 2 deletions tests/unit/Hook/Template/BuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,41 @@ public function testBuildDockerTemplate(): void
$this->assertStringContainsString('pre-commit', $code);
$this->assertStringContainsString('docker exec -i captain-container', $code);
$this->assertStringContainsString('vendor/bin/captainhook', $code);
$this->assertStringContainsString('--bootstrap=vendor/autoload.php', $code);
}

/**
* Tests Builder::build
*/
public function testBuildDockerTemplateWithoutBootstrapFromPHAR(): void
{
$repo = new DummyRepo(
[],
[
'captainhook.json' => '{}',
'vendor' => [
'autoload.php' => '',
'bin' => [
'captainhook' => ''
]
]
]
);

$resolver = $this->createResolverMock($repo->getRoot() . '/vendor/bin/captainhook', true);
$repository = $this->createRepositoryMock($repo->getRoot());
$config = $this->createConfigMock(true, $repo->getRoot() . '/captainhook.json');
$runConfig = new Run(['mode' => 'docker', 'exec' => 'docker exec captain-container', 'path' => '']);
$config->method('getRunConfig')->willReturn($runConfig);
$config->method('getBootstrap')->willReturn('');

$template = Builder::build($config, $repository, $resolver);
$this->assertInstanceOf(Docker::class, $template);

$code = $template->getCode('pre-commit');
$this->assertStringContainsString('pre-commit', $code);
$this->assertStringContainsString('docker exec -i captain-container', $code);
$this->assertStringNotContainsString('--bootstrap=', $code);
}

/**
Expand Down Expand Up @@ -91,6 +126,26 @@ public function testBuildDockerTemplateWithBinaryOutsideRepo(): void
$this->assertStringContainsString($executable, $code);
}

/**
* Tests Builder::build
*/
public function testBuildLocalTemplateWithoutBootstrapFromPHAR(): void
{
$resolver = $this->createResolverMock(CH_PATH_FILES . '/bin/captainhook', true);
$repository = $this->createRepositoryMock(CH_PATH_FILES);
$config = $this->createConfigMock(true, CH_PATH_FILES . '/template/captainhook.json');
$runConfig = new Run(['mode' => 'shell', 'exec' => '', 'path' => '']);
$config->method('getRunConfig')->willReturn($runConfig);
$config->method('getBootstrap')->willReturn('');

$template = Builder::build($config, $repository, $resolver);
$this->assertInstanceOf(Local\Shell::class, $template);

$code = $template->getCode('pre-commit');
$this->assertStringContainsString('pre-commit', $code);
$this->assertStringNotContainsString('--bootstrap=', $code);
}

/**
* Tests Builder::build
*/
Expand All @@ -110,6 +165,7 @@ public function testBuildLocalTemplate(): void
$this->assertStringContainsString('pre-commit', $code);
$this->assertStringContainsString('$captainHook->run', $code);
}

/**
* Tests Builder::build
*/
Expand Down Expand Up @@ -137,7 +193,7 @@ public function testBuildBootstrapNotFound(): void
{
$this->expectException(Exception::class);

$resolver = $this->createResolverMock(CH_PATH_FILES . '/bin/captainhook', false);
$resolver = $this->createResolverMock(CH_PATH_FILES . '/bin/captainhook');
$repository = $this->createRepositoryMock(CH_PATH_FILES);
$config = $this->createConfigMock(true, CH_PATH_FILES . '/template/captainhook.json');
$runConfig = new Run(['mode' => 'php', 'exec' => '', 'path' => '']);
Expand All @@ -159,7 +215,7 @@ public function testBuildInvalidVendor(): void
{
$this->expectException(Exception::class);

$resolver = $this->createResolverMock('./captainhook', false);
$resolver = $this->createResolverMock('./captainhook');
$repository = $this->createRepositoryMock(CH_PATH_FILES . '/config');
$config = $this->createConfigMock(true, CH_PATH_FILES . '/config/valid.json');
$runConfig = new Run(['mode' => 'php', 'exec' => '', 'path' => '']);
Expand Down
Loading

0 comments on commit 59708d7

Please sign in to comment.