diff --git a/app/Commands/Audit.php b/app/Commands/Audit.php index 155a876..208a102 100644 --- a/app/Commands/Audit.php +++ b/app/Commands/Audit.php @@ -35,8 +35,8 @@ public function handle(): int ['getGlobalComposerBinDir', Platform::getGlobalComposerBinDir()], ['isWindows', $platform->isWindows() ? 'yes' : 'no'], ['isNotWindows', $platform->isNotWindows() ? 'yes' : 'no'], - ['gitIsInitialized', $platform->gitIsInitialized() ? 'yes' : 'no'], - ['gitIsNotInitialized', $platform->gitIsNotInitialized() ? 'yes' : 'no'], + ['gitIsInitialized', Platform::gitIsInitialized() ? 'yes' : 'no'], + ['gitIsNotInitialized', Platform::gitIsNotInitialized() ? 'yes' : 'no'], ['- global -', ''], ['base_path', base_path()], ['normalized base_path', Platform::normalizePath(base_path())], diff --git a/app/Commands/Install.php b/app/Commands/Install.php index 678db4e..bb9fbc7 100644 --- a/app/Commands/Install.php +++ b/app/Commands/Install.php @@ -22,7 +22,7 @@ public function __construct( public function handle(): int { - if ($this->platform->gitIsNotInitialized()) { + if (Platform::gitIsNotInitialized()) { $this->error('Git has not been initialized in this project, aborting...'); return Command::FAILURE; @@ -73,8 +73,7 @@ public function handle(): int if ($this->option('verbose')) { $this->info('Verifying hooks are executable...'); } - exec('chmod +x '.Platform::cwd('.git/hooks').'/*'); - exec('chmod +x '.Whisky::base_path('bin/run-hook')); + exec('chmod +x '.Platform::git_path('hooks').'/*'); } $this->info('Git hooks installed successfully.'); diff --git a/app/Commands/Run.php b/app/Commands/Run.php index dc206a3..d461977 100644 --- a/app/Commands/Run.php +++ b/app/Commands/Run.php @@ -25,8 +25,8 @@ public function handle(): int return Command::FAILURE; } - if (File::exists(Platform::cwd('.git/hooks/skip-once'))) { - File::delete(Platform::cwd('.git/hooks/skip-once')); + if (File::exists(Platform::git_path('hooks/skip-once'))) { + File::delete(Platform::git_path('hooks/skip-once')); return Command::SUCCESS; } diff --git a/app/Commands/SkipOnce.php b/app/Commands/SkipOnce.php index 2e04c34..982f023 100644 --- a/app/Commands/SkipOnce.php +++ b/app/Commands/SkipOnce.php @@ -14,7 +14,7 @@ class SkipOnce extends Command public function handle(): int { - File::put(Platform::cwd('.git/hooks/skip-once'), ''); + File::put(Platform::git_path('hooks/skip-once'), ''); $this->info('Next hook will be skipped.'); $this->line('If the action you\'re about to take has a `pre` and `post` hook'); diff --git a/app/Hook.php b/app/Hook.php index 0dec3bd..a8f43ff 100644 --- a/app/Hook.php +++ b/app/Hook.php @@ -29,7 +29,7 @@ public function __construct( public function uninstall(): bool { - $path = Platform::cwd(".git/hooks/{$this->hook}"); + $path = Platform::git_path("hooks/{$this->hook}"); if ($this->fileIsMissing()) { // This should be unreachable. @@ -53,7 +53,7 @@ public function uninstall(): bool // use ensureFileExists instead? public function fileExists(): bool { - return File::exists(Platform::cwd(".git/hooks/{$this->hook}")); + return File::exists(Platform::git_path("hooks/{$this->hook}")); } public function fileIsMissing(): bool @@ -82,7 +82,7 @@ public function isNotEnabled(): bool */ public function enable(): void { - File::put(Platform::cwd(".git/hooks/{$this->hook}"), '#!/bin/sh'.PHP_EOL); + File::put(Platform::git_path("hooks/{$this->hook}"), '#!/bin/sh'.PHP_EOL); } /** @@ -101,7 +101,7 @@ public function ensureExecutable(): void public function isInstalled(): bool { return Str::contains( - File::get(Platform::cwd(".git/hooks/{$this->hook}")), + File::get(Platform::git_path("hooks/{$this->hook}")), $this->getSnippets()->toArray(), ); } @@ -112,7 +112,7 @@ public function isInstalled(): bool public function install(): void { File::append( - Platform::cwd(".git/hooks/{$this->hook}"), + Platform::git_path("hooks/{$this->hook}"), $this->getSnippets()->first().PHP_EOL, ); } @@ -139,9 +139,12 @@ public function getScripts(): Collection */ public function getSnippets(): Collection { + $cwd = Platform::cwd(); + return collect([ - "{$this->bin} run {$this->hook} \"$1\"", + "pushd {$cwd} && {$this->bin} run {$this->hook} \"$1\" && popd", // Legacy Snippets. + "{$this->bin} run {$this->hook} \"$1\"", "{$this->bin} run {$this->hook}", "eval \"$({$this->bin} get-run-cmd {$this->hook})\"", "eval \"$(./vendor/bin/whisky get-run-cmd {$this->hook})\"", @@ -172,7 +175,7 @@ public static function all(int $flags = self::FROM_CONFIG): Collection $result = collect(); if ($flags & self::FROM_GIT) { - $result->push(...collect(File::files(Platform::cwd('.git/hooks'))) + $result->push(...collect(File::files(Platform::git_path('hooks'))) ->map(fn (SplFileInfo $file) => $file->getFilename()) ->filter(fn (string $filename) => ! str_ends_with($filename, 'sample')) ); diff --git a/app/Platform.php b/app/Platform.php index 2117221..bae2d74 100644 --- a/app/Platform.php +++ b/app/Platform.php @@ -2,10 +2,13 @@ namespace ProjektGopher\Whisky; -use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Process; class Platform { + // Possibly use `git rev-parse --absolute-git-dir` instead for consistency. + const GIT_DIR_CMD = 'git rev-parse --git-dir'; + public static function cwd(string $path = ''): string { if ($path) { @@ -33,6 +36,24 @@ public static function normalizePath(string $path): string return $path; } + public static function git_path(string $path = ''): ?string + { + /** + * We use the `Process` facade here to run this + * command instead of `shell_exec()` because + * it's easier to mock in our test suite. + */ + $output = Process::run(static::GIT_DIR_CMD); + + if ($output->failed()) { + return null; + } + + return empty($path) === true + ? static::normalizePath(rtrim($output->output(), "\n")) + : static::normalizePath(rtrim($output->output(), "\n")."/{$path}"); + } + public static function getGlobalComposerHome(): string { return rtrim(shell_exec('composer -n global config home --quiet'), "\n"); @@ -58,13 +79,13 @@ public function isNotWindows(): bool return ! $this->isWindows(); } - public function gitIsInitialized(): bool + public static function gitIsInitialized(): bool { - return File::exists(Platform::cwd('.git')); + return static::git_path() !== null; } - public function gitIsNotInitialized(): bool + public static function gitIsNotInitialized(): bool { - return ! $this->gitIsInitialized(); + return ! static::gitIsInitialized(); } } diff --git a/tests/Feature/InstallTest.php b/tests/Feature/InstallTest.php index 8be21c6..d95b35e 100644 --- a/tests/Feature/InstallTest.php +++ b/tests/Feature/InstallTest.php @@ -1,6 +1,8 @@ artisan('list') @@ -9,10 +11,15 @@ }); it('fails if git is not initialized', function () { - File::shouldReceive('exists') + Process::shouldReceive('run') ->once() - ->with(normalizePath(base_path('.git'))) - ->andReturnFalse(); + ->with(Platform::GIT_DIR_CMD) + ->andReturn(new FakeProcessResult( + command: Platform::GIT_DIR_CMD, + exitCode: 1, + output: '', + errorOutput: 'fatal: not a git repository (or any of the parent directories): .git\n', + )); $this->artisan('install') ->expectsOutputToContain('Git has not been initialized in this project, aborting...') diff --git a/tests/Feature/RunTest.php b/tests/Feature/RunTest.php index db46d02..59b58d1 100644 --- a/tests/Feature/RunTest.php +++ b/tests/Feature/RunTest.php @@ -11,12 +11,12 @@ File::shouldReceive('exists') ->once() - ->with(Platform::cwd('.git/hooks/skip-once')) + ->with(Platform::git_path('hooks/skip-once')) ->andReturnTrue(); File::shouldReceive('delete') ->once() - ->with(Platform::cwd('.git/hooks/skip-once')) + ->with(Platform::git_path('hooks/skip-once')) ->andReturnTrue(); $tmp_file = Platform::temp_test_path('whisky_test_commit_msg'); @@ -47,7 +47,7 @@ ->andReturnFalse(); File::shouldReceive('exists') - ->with(Platform::cwd('.git/hooks/skip-once')) + ->with(Platform::git_path('hooks/skip-once')) ->andReturnFalse(); $tmp_file = Platform::temp_test_path('whisky_test_commit_msg'); @@ -85,7 +85,7 @@ ->andReturnFalse(); File::shouldReceive('exists') - ->with(Platform::cwd('.git/hooks/skip-once')) + ->with(Platform::git_path('hooks/skip-once')) ->andReturnFalse(); $tmp_file = Platform::temp_test_path('whisky_test_pre_commit'); @@ -115,7 +115,7 @@ ->andReturnFalse(); File::shouldReceive('exists') - ->with(Platform::cwd('.git/hooks/skip-once')) + ->with(Platform::git_path('hooks/skip-once')) ->andReturnFalse(); $tmp_file = Platform::temp_test_path('whisky_test_commit_msg'); diff --git a/tests/Feature/SkipOnceTest.php b/tests/Feature/SkipOnceTest.php index 9cbcffe..e941778 100644 --- a/tests/Feature/SkipOnceTest.php +++ b/tests/Feature/SkipOnceTest.php @@ -14,8 +14,8 @@ ->expectsOutputToContain('Next hook will be skipped.') ->assertExitCode(0); - expect(File::exists(Platform::cwd('.git/hooks/skip-once')))->toBeTrue(); + expect(File::exists(Platform::git_path('hooks/skip-once')))->toBeTrue(); // Cleanup - File::delete(Platform::cwd('.git/hooks/skip-once')); + File::delete(Platform::git_path('hooks/skip-once')); });