Skip to content

Commit 20da5de

Browse files
[git-hooks] Make packaged hooks canonical (#92) (#93)
* Make packaged git hooks canonical * Update wiki submodule pointer for PR #93 * Fix canonical symlink assertion * Corrects the chmod call to use the numeric format instead of octal in hook installation commands. Signed-off-by: Felipe Sayão Lobato Abreu <github@mentordosnerds.com> * Updates GrumPHP hooks to use the exec command directly and removes the ENV variable. Signed-off-by: Felipe Sayão Lobato Abreu <github@mentordosnerds.com> --------- Signed-off-by: Felipe Sayão Lobato Abreu <github@mentordosnerds.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 47a2b31 commit 20da5de

10 files changed

Lines changed: 93 additions & 82 deletions

File tree

.github/wiki

Submodule wiki updated from db93e83 to 446b1c6

docs/commands/git-hooks.rst

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
git-hooks
22
=========
33

4-
Installs Fast Forward Git hooks and initializes GrumPHP.
4+
Installs packaged Fast Forward Git hooks.
55

66
Description
77
-----------
88

9-
The ``git-hooks`` command installs Git hooks into the repository and optionally
10-
runs GrumPHP initialization. It:
9+
The ``git-hooks`` command installs the hook templates maintained in
10+
``resources/git-hooks`` into the repository. It:
1111

12-
1. Runs ``grumphp git:init`` to register hooks with GrumPHP (unless ``--skip-grumphp-init``)
13-
2. Copies hook files from a source directory to the target hooks directory
14-
3. Sets executable permissions on copied hooks
12+
1. Copies hook files from a source directory to the target hooks directory
13+
2. Sets executable permissions on copied hooks
1514

1615
Usage
1716
-----
@@ -32,9 +31,6 @@ Options
3231
``--target, -t`` (optional)
3332
Path to the target Git hooks directory. Default: ``.git/hooks``.
3433

35-
``--skip-grumphp-init``
36-
Skip running ``grumphp git:init`` before copying hooks.
37-
3834
``--no-overwrite``
3935
Do not overwrite existing hook files.
4036

@@ -47,12 +43,6 @@ Install hooks with defaults:
4743
4844
composer git-hooks
4945
50-
Install hooks without running GrumPHP init:
51-
52-
.. code-block:: bash
53-
54-
composer git-hooks --skip-grumphp-init
55-
5646
Install hooks without overwriting existing ones:
5747

5848
.. code-block:: bash
@@ -70,4 +60,4 @@ Exit Codes
7060
* - 0
7161
- Success. Hooks installed successfully.
7262
* - 1
73-
- Failure. GrumPHP init failed or copy error.
63+
- Failure. Copy error.

docs/running/specialized-commands.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,15 +258,14 @@ Important details:
258258
``git-hooks``
259259
-------------
260260

261-
Installs Fast Forward Git hooks and initializes GrumPHP.
261+
Installs packaged Fast Forward Git hooks.
262262

263263
.. code-block:: bash
264264
265-
composer git-hooks --skip-grumphp-init
265+
composer git-hooks
266266
267267
Important details:
268268

269-
- runs ``grumphp git:init`` to register hooks with GrumPHP by default;
270269
- copies hook files from source to target directory;
271270
- sets executable permissions on copied hooks;
272271
- ``--source`` defaults to ``resources/git-hooks``;

resources/git-hooks/commit-msg

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/sh
2+
3+
#
4+
# Run the hook command.
5+
# Note: this will be replaced by the real command during copy.
6+
#
7+
8+
GIT_USER=$(git config user.name)
9+
GIT_EMAIL=$(git config user.email)
10+
COMMIT_MSG_FILE=$1
11+
12+
# Fetch the GIT diff and format it as command input:
13+
DIFF=$(git -c diff.mnemonicprefix=false -c diff.noprefix=false --no-pager diff --raw -r -p -m -M --full-index --no-color --staged | cat)
14+
15+
# Grumphp env vars
16+
17+
export GRUMPHP_GIT_WORKING_DIR="$(git rev-parse --show-toplevel)"
18+
19+
# Run GrumPHP
20+
(cd "./" && printf "%s\n" "${DIFF}" | exec 'vendor/bin/grumphp.phar' 'git:commit-msg' "--git-user='$GIT_USER'" "--git-email='$GIT_EMAIL'" "$COMMIT_MSG_FILE")

resources/git-hooks/post-checkout

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,35 @@
22

33
#
44
# DevTools post-checkout hook
5-
# Runs composer update if composer.json was modified after checkout
5+
# Runs composer update if composer.json or composer.lock changed after checkout
66
#
77

88
PREV_HEAD="$1"
99
NEW_HEAD="$2"
1010
CHECKOUT_TYPE="$3"
1111

12-
if [ "$CHECKOUT_TYPE" = "1" ]; then
13-
if git diff --quiet --cached "$PREV_HEAD" "$NEW_HEAD" -- composer.json; then
14-
if git diff --quiet "$PREV_HEAD" "$NEW_HEAD" -- composer.json; then
15-
exit 0
16-
fi
17-
fi
12+
GIT_TOPLEVEL=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
13+
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || exit 0
1814

19-
echo "DevTools: composer.json changed, running composer update..."
20-
(cd "$(git rev-parse --show-toplevel)" && composer update --minimal-changes --no-interaction) || true
15+
export DEVTOOLS_GIT_WORKING_DIR="$GIT_TOPLEVEL"
16+
export DEVTOOLS_GIT_DIR="$GIT_DIR"
17+
export DEVTOOLS_GIT_HOOK="post-checkout"
18+
export DEVTOOLS_GIT_PREV_HEAD="$PREV_HEAD"
19+
export DEVTOOLS_GIT_NEW_HEAD="$NEW_HEAD"
20+
export DEVTOOLS_GIT_CHECKOUT_TYPE="$CHECKOUT_TYPE"
21+
22+
if [ "$CHECKOUT_TYPE" != "1" ]; then
23+
exit 0
24+
fi
25+
26+
CHANGED_FILES=$(git diff --name-only "$PREV_HEAD" "$NEW_HEAD" 2>/dev/null || true)
27+
28+
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(composer\.json|composer\.lock)$'; then
29+
echo "DevTools: composer manifest changed after checkout, running composer update..."
30+
(
31+
cd "$GIT_TOPLEVEL" || exit 1
32+
composer update --minimal-changes --no-interaction
33+
) || true
2134
fi
2235

2336
exit 0

resources/git-hooks/post-merge

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,25 @@
22

33
#
44
# DevTools post-merge hook
5-
# Runs composer update if composer.json was modified after merge
5+
# Runs composer update if composer.json or composer.lock changed after merge
66
#
77

8-
if git diff --cached --name-only --diff-filter=M | grep -q "^composer.json$"; then
9-
echo "DevTools: composer.json modified in merge, running composer update..."
10-
(cd "$(git rev-parse --show-toplevel)" && composer update --minimal-changes --no-interaction) || true
8+
GIT_TOPLEVEL=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
9+
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || exit 0
10+
11+
export DEVTOOLS_GIT_WORKING_DIR="$GIT_TOPLEVEL"
12+
export DEVTOOLS_GIT_DIR="$GIT_DIR"
13+
export DEVTOOLS_GIT_HOOK="post-merge"
14+
export DEVTOOLS_GIT_POST_MERGE_SQUASH=${1:-0}
15+
16+
CHANGED_FILES=$(git diff --name-only ORIG_HEAD HEAD 2>/dev/null || true)
17+
18+
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(composer\.json|composer\.lock)$'; then
19+
echo "DevTools: composer manifest changed after merge, running composer update..."
20+
(
21+
cd "$GIT_TOPLEVEL" || exit 1
22+
composer update --minimal-changes --no-interaction
23+
) || true
1124
fi
1225

1326
exit 0

resources/git-hooks/pre-commit

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/sh
2+
3+
#
4+
# Run the hook command.
5+
# Note: this will be replaced by the real command during copy.
6+
#
7+
8+
# Fetch the GIT diff and format it as command input:
9+
DIFF=$(git -c diff.mnemonicprefix=false -c diff.noprefix=false --no-pager diff --raw -r -p -m -M --full-index --no-color --staged | cat)
10+
11+
# Grumphp env vars
12+
13+
export GRUMPHP_GIT_WORKING_DIR="$(git rev-parse --show-toplevel)"
14+
15+
# Run GrumPHP
16+
(cd "./" && printf "%s\n" "${DIFF}" | exec 'vendor/bin/grumphp.phar' 'git:pre-commit' '--skip-success-output')

src/Console/Command/GitHooksCommand.php

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
use Composer\Command\BaseCommand;
2323
use FastForward\DevTools\Filesystem\FinderFactoryInterface;
2424
use FastForward\DevTools\Filesystem\FilesystemInterface;
25-
use FastForward\DevTools\Process\ProcessBuilderInterface;
26-
use FastForward\DevTools\Process\ProcessQueueInterface;
2725
use Symfony\Component\Config\FileLocatorInterface;
2826
use Symfony\Component\Console\Attribute\AsCommand;
2927
use Symfony\Component\Console\Input\InputInterface;
@@ -32,12 +30,12 @@
3230
use Symfony\Component\Filesystem\Path;
3331

3432
/**
35-
* Installs Git hooks and initializes GrumPHP hooks for the consumer repository.
33+
* Installs packaged Git hooks for the consumer repository.
3634
*/
3735
#[AsCommand(
3836
name: 'git-hooks',
3937
description: 'Installs Fast Forward Git hooks.',
40-
help: 'This command runs GrumPHP hook initialization and copies packaged Git hooks into the current repository.'
38+
help: 'This command copies packaged Git hooks into the current repository.'
4139
)]
4240
final class GitHooksCommand extends BaseCommand
4341
{
@@ -46,15 +44,11 @@ final class GitHooksCommand extends BaseCommand
4644
*
4745
* @param FilesystemInterface $filesystem the filesystem used to copy hooks
4846
* @param FileLocatorInterface $fileLocator the locator used to find packaged hooks
49-
* @param ProcessBuilderInterface $processBuilder the builder used to assemble GrumPHP processes
50-
* @param ProcessQueueInterface $processQueue the queue used to execute GrumPHP initialization
5147
* @param FinderFactoryInterface $finderFactory the factory used to create finders for hook files
5248
*/
5349
public function __construct(
5450
private readonly FilesystemInterface $filesystem,
5551
private readonly FileLocatorInterface $fileLocator,
56-
private readonly ProcessBuilderInterface $processBuilder,
57-
private readonly ProcessQueueInterface $processQueue,
5852
private readonly FinderFactoryInterface $finderFactory,
5953
) {
6054
parent::__construct();
@@ -80,11 +74,6 @@ protected function configure(): void
8074
description: 'Path to the target Git hooks directory.',
8175
default: '.git/hooks',
8276
)
83-
->addOption(
84-
name: 'skip-grumphp-init',
85-
mode: InputOption::VALUE_NONE,
86-
description: 'Skip running grumphp git:init before copying hooks.',
87-
)
8877
->addOption(
8978
name: 'no-overwrite',
9079
mode: InputOption::VALUE_NONE,
@@ -93,7 +82,7 @@ protected function configure(): void
9382
}
9483

9584
/**
96-
* Initializes GrumPHP hooks and copies packaged hooks.
85+
* Copies packaged Git hooks.
9786
*
9887
* @param InputInterface $input the command input
9988
* @param OutputInterface $output the command output
@@ -102,18 +91,6 @@ protected function configure(): void
10291
*/
10392
protected function execute(InputInterface $input, OutputInterface $output): int
10493
{
105-
if (! $input->getOption('skip-grumphp-init')) {
106-
$this->processQueue->add(
107-
$this->processBuilder
108-
->withArgument('git:init')
109-
->build('vendor/bin/grumphp')
110-
);
111-
112-
if (self::SUCCESS !== $this->processQueue->run($output)) {
113-
return self::FAILURE;
114-
}
115-
}
116-
11794
$sourcePath = $this->fileLocator->locate((string) $input->getOption('source'));
11895
$targetPath = (string) $this->filesystem->getAbsolutePath((string) $input->getOption('target'));
11996
$overwrite = ! $input->getOption('no-overwrite');
@@ -133,7 +110,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
133110
}
134111

135112
$this->filesystem->copy($file->getRealPath(), $hookPath, $overwrite);
136-
$this->filesystem->chmod($hookPath, 0o755, 0o755);
113+
$this->filesystem->chmod($hookPath, 755, 0o755);
137114

138115
$output->writeln(\sprintf('<info>Installed %s hook.</info>', $file->getFilename()));
139116
}

tests/Console/Command/GitHooksCommandTest.php

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@
2222
use FastForward\DevTools\Console\Command\GitHooksCommand;
2323
use FastForward\DevTools\Filesystem\FinderFactoryInterface;
2424
use FastForward\DevTools\Filesystem\FilesystemInterface;
25-
use FastForward\DevTools\Process\ProcessBuilder;
26-
use FastForward\DevTools\Process\ProcessQueueInterface;
2725
use PHPUnit\Framework\Attributes\CoversClass;
2826
use PHPUnit\Framework\Attributes\Test;
29-
use PHPUnit\Framework\Attributes\UsesClass;
3027
use PHPUnit\Framework\TestCase;
3128
use Prophecy\Argument;
3229
use Prophecy\PhpUnit\ProphecyTrait;
@@ -36,15 +33,13 @@
3633
use Symfony\Component\Console\Input\InputInterface;
3734
use Symfony\Component\Console\Output\OutputInterface;
3835
use Symfony\Component\Finder\Finder;
39-
use Symfony\Component\Process\Process;
4036

4137
use function Safe\mkdir;
4238
use function Safe\file_put_contents;
4339
use function Safe\unlink;
4440
use function Safe\rmdir;
4541

4642
#[CoversClass(GitHooksCommand::class)]
47-
#[UsesClass(ProcessBuilder::class)]
4843
final class GitHooksCommandTest extends TestCase
4944
{
5045
use ProphecyTrait;
@@ -55,8 +50,6 @@ final class GitHooksCommandTest extends TestCase
5550

5651
private ObjectProphecy $finderFactory;
5752

58-
private ObjectProphecy $processQueue;
59-
6053
private ObjectProphecy $input;
6154

6255
private ObjectProphecy $output;
@@ -77,15 +70,12 @@ protected function setUp(): void
7770
$this->filesystem = $this->prophesize(FilesystemInterface::class);
7871
$this->fileLocator = $this->prophesize(FileLocatorInterface::class);
7972
$this->finderFactory = $this->prophesize(FinderFactoryInterface::class);
80-
$this->processQueue = $this->prophesize(ProcessQueueInterface::class);
8173
$this->input = $this->prophesize(InputInterface::class);
8274
$this->output = $this->prophesize(OutputInterface::class);
8375

8476
$this->command = new GitHooksCommand(
8577
$this->filesystem->reveal(),
8678
$this->fileLocator->reveal(),
87-
new ProcessBuilder(),
88-
$this->processQueue->reveal(),
8979
$this->finderFactory->reveal(),
9080
);
9181
}
@@ -110,7 +100,7 @@ public function commandWillSetExpectedNameDescriptionAndHelp(): void
110100
self::assertSame('git-hooks', $this->command->getName());
111101
self::assertSame('Installs Fast Forward Git hooks.', $this->command->getDescription());
112102
self::assertSame(
113-
'This command runs GrumPHP hook initialization and copies packaged Git hooks into the current repository.',
103+
'This command copies packaged Git hooks into the current repository.',
114104
$this->command->getHelp()
115105
);
116106
}
@@ -119,23 +109,15 @@ public function commandWillSetExpectedNameDescriptionAndHelp(): void
119109
* @return void
120110
*/
121111
#[Test]
122-
public function executeWillRunGrumPhpInitAndCopyHooks(): void
112+
public function executeWillCopyPackagedHooks(): void
123113
{
124-
$this->input->getOption('skip-grumphp-init')
125-
->willReturn(false);
126114
$this->input->getOption('source')
127115
->willReturn('resources/git-hooks');
128116
$this->input->getOption('target')
129117
->willReturn('.git/hooks');
130118
$this->input->getOption('no-overwrite')
131119
->willReturn(false);
132120

133-
$this->processQueue->add(Argument::type(Process::class))
134-
->shouldBeCalledOnce();
135-
$this->processQueue->run($this->output->reveal())
136-
->willReturn(GitHooksCommand::SUCCESS)
137-
->shouldBeCalledOnce();
138-
139121
$this->fileLocator->locate('resources/git-hooks')
140122
->willReturn($this->sourceDirectory);
141123
$this->finderFactory->create()
@@ -145,7 +127,7 @@ public function executeWillRunGrumPhpInitAndCopyHooks(): void
145127
->willReturn('/app/.git/hooks');
146128
$this->filesystem->copy(Argument::containingString('/post-merge'), '/app/.git/hooks/post-merge', true)
147129
->shouldBeCalledOnce();
148-
$this->filesystem->chmod('/app/.git/hooks/post-merge', 0o755, 0o755)
130+
$this->filesystem->chmod('/app/.git/hooks/post-merge', 755, 0o755)
149131
->shouldBeCalledOnce();
150132

151133
self::assertSame(GitHooksCommand::SUCCESS, $this->executeCommand());

tests/Filesystem/FilesystemTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
use function Safe\file_put_contents;
2929
use function Safe\getcwd;
30+
use function Safe\realpath;
3031

3132
#[CoversClass(Filesystem::class)]
3233
final class FilesystemTest extends TestCase
@@ -180,6 +181,6 @@ public function symlinkAndReadlinkWillUseAbsolutePaths(): void
180181
$this->filesystem->mkdir($origin);
181182
$this->filesystem->symlink($origin, $target);
182183

183-
self::assertSame($origin, $this->filesystem->readlink($target, true));
184+
self::assertSame(realpath($origin), $this->filesystem->readlink($target, true));
184185
}
185186
}

0 commit comments

Comments
 (0)