Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,27 @@ a local git bundle. Using a bundle instead of a network clone keeps the tests fa
fully offline.

If you need a new fixture scenario (a specific merge, encoding, or signed-commit shape,
for example), regenerate the bundle locally, entirely from the one already in the repo:
for example), regenerate the bundle with `tests/fixtures/generate-bundle.php`, entirely
from the one already in the repo:

```bash
$ git clone tests/fixtures/foobar.bundle /tmp/foobar-fixture && cd /tmp/foobar-fixture
$ for b in $(git branch -r | grep -v HEAD | sed 's#origin/##'); do
$ git branch --track "$b" "origin/$b"
$ done
# ... add your commits, branches or tags ...
$ git bundle create foobar.bundle \
HEAD refs/heads/master refs/heads/new-feature refs/heads/diff-features \
refs/heads/pagination refs/heads/path-resolving refs/tags/0.1 refs/tags/annotated
$ cp foobar.bundle /path/to/gitlib/tests/fixtures/foobar.bundle
$ php tests/fixtures/generate-bundle.php extract
# ... add your commits, branches or tags in the printed directory ...
$ php tests/fixtures/generate-bundle.php build /path/printed/above
```

`extract` clones the current bundle to a working directory with every branch checked out
locally, ready to receive new commits. `build` rebuilds `tests/fixtures/foobar.bundle`
from that directory, restricted to the refs listed in `tests/fixtures/bundle-refs.txt` —
add your new branch or tag there first if you introduced one.

Then update the commit SHA constants in `AbstractTestCase` to match, and run
`tests/fixtures/verify-bundle.sh`. It checks the bundle's integrity, its ref list against
an allow-list, and its size, since GitHub renders any change to this binary file as an
`bundle-refs.txt`, and its size, since GitHub renders any change to this binary file as an
opaque diff. If your change intentionally adds a ref or grows the file, update
`ALLOWED_REFS` or `MAX_SIZE_KB` in that script as part of the same pull request, so the
reason for the change is explicit and reviewable rather than a silent binary diff.
`tests/fixtures/bundle-refs.txt` or `MAX_SIZE_KB` in that script as part of the same pull
request, so the reason for the change is explicit and reviewable rather than a silent
binary diff.

## Standard code

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"require-dev": {
"ext-fileinfo": "*",
"phpunit/phpunit": "^12.0",
"psr/log": "^3.0"
"psr/log": "^3.0",
"symfony/console": "^8.1"
},
"config": {
"preferred-install": "dist",
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/bundle-refs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
HEAD
refs/heads/master
refs/heads/new-feature
refs/heads/diff-features
refs/heads/pagination
refs/heads/path-resolving
refs/tags/0.1
refs/tags/annotated
133 changes: 133 additions & 0 deletions tests/fixtures/generate-bundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env php
<?php

/*
* This file is part of Gitonomy.
*
* (c) Alexandre Salomé <alexandre.salome@gmail.com>
* (c) Julien DIDIER <genzo.wm@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

require dirname(__DIR__, 2).'/vendor/autoload.php';

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Process;

const BUNDLE_PATH = __DIR__.'/foobar.bundle';
const REFS_PATH = __DIR__.'/bundle-refs.txt';

function git(array $args, ?string $cwd = null): Process
{
$process = new Process(['git', ...$args], $cwd);
$process->mustRun();

return $process;
}

function bundleRefs(): array
{
return array_values(array_filter(explode("\n", trim(file_get_contents(REFS_PATH)))));
}

#[AsCommand(name: 'extract', description: 'Clone the fixture bundle to a working directory, ready for new commits, branches or tags')]
final class ExtractCommand extends Command
{
protected function configure(): void
{
$this->addArgument('path', InputArgument::OPTIONAL, 'Where to clone the fixture', sys_get_temp_dir().'/foobar-fixture-'.bin2hex(random_bytes(4)));
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$dest = $input->getArgument('path');

if (file_exists($dest)) {
$io->error(sprintf('Destination "%s" already exists.', $dest));

return Command::FAILURE;
}

git(['clone', '--quiet', BUNDLE_PATH, $dest]);

$localBranches = explode("\n", trim(git(['branch', '--format=%(refname:short)'], $dest)->getOutput()));

// Full ref names, not `--format=%(refname:short)`: git shortens the symbolic
// refs/remotes/origin/HEAD pointer to a bare "origin" on some git versions, which
// would otherwise be mistaken for a real branch called "origin".
$refs = trim(git(['for-each-ref', '--format=%(refname)', 'refs/remotes/origin'], $dest)->getOutput());
foreach (explode("\n", $refs) as $ref) {
if ('' === $ref) {
continue;
}
$local = preg_replace('#^refs/remotes/origin/#', '', $ref);
if ('HEAD' === $local || in_array($local, $localBranches, true)) {
// Symbolic HEAD pointer, or already checked out as the clone's default branch.
continue;
}
git(['branch', '--track', $local, "origin/{$local}"], $dest);
}

$io->success('Fixture extracted.');
$io->writeln([
sprintf('Path: %s', $dest),
'',
'Make your changes there (commits, branches, tags), then run:',
sprintf(' %s build %s', $_SERVER['argv'][0], $dest),
]);

return Command::SUCCESS;
}
}

#[AsCommand(name: 'build', description: 'Rebuild tests/fixtures/foobar.bundle from a working directory produced by "extract"')]
final class BuildCommand extends Command
{
protected function configure(): void
{
$this->addArgument('path', InputArgument::REQUIRED, 'The working directory produced by "extract"');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$src = $input->getArgument('path');

if (!is_dir($src)) {
$io->error(sprintf('Source "%s" does not exist. Run "extract" first.', $src));

return Command::FAILURE;
}

$tmpBundle = tempnam(sys_get_temp_dir(), 'foobar_bundle_');
git(['bundle', 'create', $tmpBundle, ...bundleRefs()], $src);
git(['bundle', 'verify', $tmpBundle]);

copy($tmpBundle, BUNDLE_PATH);
unlink($tmpBundle);

$io->success('Bundle rebuilt.');
$io->writeln([
sprintf('Path: %s', BUNDLE_PATH),
'',
sprintf('If you added a ref not in %s, update that file first and re-run build.', REFS_PATH),
'Otherwise, update the commit constants in AbstractTestCase if needed, then run:',
' tests/fixtures/verify-bundle.sh',
]);

return Command::SUCCESS;
}
}

$app = new Application('gitlib fixture bundle tool');
$app->addCommands([new ExtractCommand(), new BuildCommand()]);
exit($app->run());
14 changes: 2 additions & 12 deletions tests/fixtures/verify-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null

BUNDLE="foobar.bundle"
MAX_SIZE_KB=200

ALLOWED_REFS="
HEAD
refs/heads/diff-features
refs/heads/master
refs/heads/new-feature
refs/heads/pagination
refs/heads/path-resolving
refs/tags/0.1
refs/tags/annotated
"
ALLOWED_REFS="$(cat bundle-refs.txt)"

echo "== Verifying $BUNDLE =="

Expand All @@ -46,7 +36,7 @@ UNEXPECTED_REFS="$(comm -23 <(echo "$ACTUAL_REFS") <(sort -u <<< "$ALLOWED_REFS"
if [ -n "$UNEXPECTED_REFS" ]; then
echo "ERROR: $BUNDLE contains refs that are not in the allow-list:" >&2
echo "$UNEXPECTED_REFS" >&2
echo "If this is expected, update ALLOWED_REFS in $0 as part of the same PR." >&2
echo "If this is expected, update tests/fixtures/bundle-refs.txt as part of the same PR." >&2
exit 1
fi

Expand Down