From 76943dde45ebc2e8c5f07b42a49c448012a4f8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 7 Sep 2026 16:36:10 +0200 Subject: [PATCH 1/4] Replace the network fixture clone with a local git bundle The test suite cloned https://github.com/gitonomy/foobar.git over the network on every run, and asserted on hardcoded commit SHAs from that external repository's history. This made the tests fail without network access, coupled them to a repository outside our control, and made it impossible for a contributor to add a new fixture scenario without push access to that separate repository. tests/fixtures/foobar.bundle is a git bundle of the same repository (all branches and tags, including the GPG-signed commit used by PushReferenceTest). Admin::cloneTo/cloneBranchTo/cloneRepository just shell out to `git clone `, so cloning from a local bundle file behaves identically to cloning the remote repository, but works offline and can't be affected by changes made upstream. --- .gitattributes | 1 + tests/Gitonomy/Git/Tests/AbstractTestCase.php | 2 +- tests/fixtures/foobar.bundle | Bin 0 -> 40744 bytes 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/foobar.bundle diff --git a/.gitattributes b/.gitattributes index 176a458f..20a19621 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ * text=auto +/tests/fixtures/*.bundle binary diff --git a/tests/Gitonomy/Git/Tests/AbstractTestCase.php b/tests/Gitonomy/Git/Tests/AbstractTestCase.php index e56bc35c..b2965905 100644 --- a/tests/Gitonomy/Git/Tests/AbstractTestCase.php +++ b/tests/Gitonomy/Git/Tests/AbstractTestCase.php @@ -18,7 +18,7 @@ abstract class AbstractTestCase extends TestCase { - public const REPOSITORY_URL = 'https://github.com/gitonomy/foobar.git'; + public const REPOSITORY_URL = __DIR__.'/../../../fixtures/foobar.bundle'; public const NO_MESSAGE_COMMIT = '011cd0c1625190d2959ee9a8f9f822006d94b661'; public const LONGFILE_COMMIT = '4f17752acc9b7c54ba679291bf24cb7d354f0f4f'; diff --git a/tests/fixtures/foobar.bundle b/tests/fixtures/foobar.bundle new file mode 100644 index 0000000000000000000000000000000000000000..bbd71de5f6720e2a11367b67a1ff64cf78a3b66f GIT binary patch literal 40744 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 ?0P!U>0{9b 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 I|11gq2YVExs{jB1 literal 0 HcmV?d00001 From 51f0f75b6a017b887359ae95b5ff301a28c825b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 7 Sep 2026 16:36:21 +0200 Subject: [PATCH 2/4] Guard the fixture bundle against unreviewable changes in CI Since tests/fixtures/foobar.bundle is a binary file, GitHub shows a PR touching it as an opaque "binary file not shown" diff: a contributor could regenerate it with extra refs or bloated content while keeping the commit SHAs the tests rely on intact, and a reviewer would have no way to see it from the diff alone. tests/fixtures/verify-bundle.sh runs `git bundle verify`, checks the ref list against an explicit allow-list of the 5 branches and 2 tags the fixture actually needs, and fails if the file grows past 200KB (current size: ~39KB). Any legitimate change to the fixture's shape or size requires editing this script in the same PR, making the change explicit and reviewable instead of silent. Wired into CI as a new verify-fixture job that runs on every push and pull request. --- .github/workflows/tests.yml | 10 +++++++ tests/fixtures/verify-bundle.sh | 53 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100755 tests/fixtures/verify-bundle.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ae4f7d70..73634aab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,6 +6,16 @@ on: pull_request: jobs: + verify-fixture: + name: Verify Test Fixture Bundle + runs-on: ubuntu-24.04 + steps: + - name: Checkout Code + uses: actions/checkout@v7 + + - name: Verify fixture bundle integrity, refs and size + run: tests/fixtures/verify-bundle.sh + check-cs: name: Check Coding Standards runs-on: ubuntu-24.04 diff --git a/tests/fixtures/verify-bundle.sh b/tests/fixtures/verify-bundle.sh new file mode 100755 index 00000000..ed775877 --- /dev/null +++ b/tests/fixtures/verify-bundle.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# +# Guards tests/fixtures/foobar.bundle against unexpected changes: any pull +# request touching it should be reviewed by hand (see CONTRIBUTING.md), but +# this catches the obvious cases automatically: +# - a corrupted or incomplete bundle +# - refs that were not in the original gitonomy/foobar fixture +# - a file that has silently grown well past its expected size +# +# Run it locally with: tests/fixtures/verify-bundle.sh + +set -euo pipefail + +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 +" + +echo "== Verifying $BUNDLE ==" + +VERIFY_OUTPUT="$(git bundle verify "$BUNDLE")" +echo "$VERIFY_OUTPUT" + +SIZE_KB=$(( $(stat -c%s "$BUNDLE") / 1024 )) +echo "Size: ${SIZE_KB}KB (limit: ${MAX_SIZE_KB}KB)" +if [ "$SIZE_KB" -gt "$MAX_SIZE_KB" ]; then + echo "ERROR: $BUNDLE is ${SIZE_KB}KB, which exceeds the ${MAX_SIZE_KB}KB limit." >&2 + echo "If this growth is expected, bump MAX_SIZE_KB in $0 as part of the same PR." >&2 + exit 1 +fi + +ACTUAL_REFS="$(grep -oE '(refs/[^ ]+|HEAD)$' <<< "$VERIFY_OUTPUT" | sort -u)" +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 + exit 1 +fi + +echo "OK: bundle structure and size are within expected bounds." From f81563b0d5d37fe182dcd3a6b24cdbc2fbcd9d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 7 Sep 2026 16:39:57 +0200 Subject: [PATCH 3/4] Document the test fixture bundle in CONTRIBUTING.md Explains what tests/fixtures/foobar.bundle is, how to regenerate it to add a new fixture scenario, and why tests/fixtures/verify-bundle.sh exists and needs updating alongside any intentional change to the bundle's refs or size. --- .github/CONTRIBUTING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 28cea4c3..ce00a4be 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -39,6 +39,34 @@ $ vendor/bin/phpunit * A script `test-git-versions.sh` is available in repository to test gitlib against many git versions. * The tests will be automatically run by [GitHub Actions](https://github.com/features/actions) against pull requests. +* Tests run fully offline: no network access is required. + +## Test fixtures + +Most tests run against a fixture repository cloned from `tests/fixtures/foobar.bundle`, +a git bundle of [gitonomy/foobar](https://github.com/gitonomy/foobar). Using a local +bundle instead of cloning the repository over the network keeps the tests fast, offline, +and independent of that repository's history. + +If you need a new fixture scenario (a specific merge, encoding, or signed-commit shape, +for example), regenerate the bundle from a clone of `gitonomy/foobar` with your changes +added, using the same set of refs already in the bundle: + +```bash +$ git clone https://github.com/gitonomy/foobar.git /tmp/foobar && cd /tmp/foobar +# ... 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 +``` + +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 +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. ## Standard code From 85a9fae70116751a3d0aada9c655812c10fd6083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 7 Sep 2026 16:48:24 +0200 Subject: [PATCH 4/4] Make the fixture regeneration recipe fully local MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docs and comments still pointed at cloning gitonomy/foobar over the network to add a new fixture scenario, defeating the point of moving to a local bundle. The bundle already contains the full history (all branches and tags), so regenerating it only ever needs to clone tests/fixtures/foobar.bundle itself — verified this reproduces a byte-identical bundle with no network access. --- .github/CONTRIBUTING.md | 13 +++++++------ tests/Gitonomy/Git/Tests/AbstractTestCase.php | 4 ++++ tests/fixtures/verify-bundle.sh | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ce00a4be..7486bbdd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -44,16 +44,17 @@ $ vendor/bin/phpunit ## Test fixtures Most tests run against a fixture repository cloned from `tests/fixtures/foobar.bundle`, -a git bundle of [gitonomy/foobar](https://github.com/gitonomy/foobar). Using a local -bundle instead of cloning the repository over the network keeps the tests fast, offline, -and independent of that repository's history. +a local git bundle. Using a bundle instead of a network clone keeps the tests fast and +fully offline. If you need a new fixture scenario (a specific merge, encoding, or signed-commit shape, -for example), regenerate the bundle from a clone of `gitonomy/foobar` with your changes -added, using the same set of refs already in the bundle: +for example), regenerate the bundle locally, entirely from the one already in the repo: ```bash -$ git clone https://github.com/gitonomy/foobar.git /tmp/foobar && cd /tmp/foobar +$ 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 \ diff --git a/tests/Gitonomy/Git/Tests/AbstractTestCase.php b/tests/Gitonomy/Git/Tests/AbstractTestCase.php index b2965905..80cd16e9 100644 --- a/tests/Gitonomy/Git/Tests/AbstractTestCase.php +++ b/tests/Gitonomy/Git/Tests/AbstractTestCase.php @@ -18,6 +18,10 @@ abstract class AbstractTestCase extends TestCase { + /** + * Local git bundle used as a fixture repository: cloning from it behaves like + * cloning a remote repository, but requires no network access. + */ public const REPOSITORY_URL = __DIR__.'/../../../fixtures/foobar.bundle'; public const NO_MESSAGE_COMMIT = '011cd0c1625190d2959ee9a8f9f822006d94b661'; diff --git a/tests/fixtures/verify-bundle.sh b/tests/fixtures/verify-bundle.sh index ed775877..f543e212 100755 --- a/tests/fixtures/verify-bundle.sh +++ b/tests/fixtures/verify-bundle.sh @@ -4,7 +4,7 @@ # request touching it should be reviewed by hand (see CONTRIBUTING.md), but # this catches the obvious cases automatically: # - a corrupted or incomplete bundle -# - refs that were not in the original gitonomy/foobar fixture +# - refs that were not in the original fixture # - a file that has silently grown well past its expected size # # Run it locally with: tests/fixtures/verify-bundle.sh