From 2ceb46a26045057d771ccf0bfceed2c508b2c198 Mon Sep 17 00:00:00 2001 From: Kieran Mann Date: Fri, 9 Oct 2026 13:37:06 -0700 Subject: [PATCH 1/8] ci: run CI for pushes to earthly-main and PRs into any branch The upstream merge took upstream's trigger block. That dropped the fork's push trigger (earthly-main, gh_test_ci) and limited pull_request to master/main. So CI no longer ran for PRs into earthly-main, including #3 itself. This brings back the push trigger from earthly-main (644b083). It also removes the branch filter from pull_request, so the workflow runs for every PR, whatever its base. A PR into earthly-main would have been enough, but stacked PRs (like this one, based on chore-merge-v0.14.1) need CI too. We keep upstream's workflow_dispatch and schedule triggers. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit f2e55f54a795f34aa3e5dc34a8377c7f0df4b6fb) --- .github/workflows/ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 283c4d4..75176d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,13 @@ on: workflow_dispatch: schedule: - cron: '0 8 */6 * *' # every 6 days - pull_request: + push: branches: - - master - - main + - earthly-main + - gh_test_ci + # No branch filter, so PRs stacked on other PR branches + # (not just PRs into earthly-main) get CI too. + pull_request: jobs: validate: From 4b6ce428ef10b1098f017ae32f8355b3803ca9fd Mon Sep 17 00:00:00 2001 From: Kieran Mann Date: Fri, 9 Oct 2026 13:47:40 -0700 Subject: [PATCH 2/8] ci: pin actions to full-length commit SHAs The EarthBuild org requires every action to be pinned to a full commit SHA; with tag pins every job failed at "Set up job" before running. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit e66a226757f77ce33b857dccf7a9e8fb8ee7c2de) --- .github/workflows/ci.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75176d1..f061cf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,13 +31,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Run - uses: docker/bake-action@v4 + uses: docker/bake-action@aefd381cbaa93c62a1e8b02194ae420cc36269d2 # v4.7.0 with: targets: ${{ matrix.target }} @@ -51,13 +51,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Run - uses: docker/bake-action@v4 + uses: docker/bake-action@aefd381cbaa93c62a1e8b02194ae420cc36269d2 # v4.7.0 with: targets: ${{ matrix.target }} @@ -76,18 +76,18 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Test - uses: docker/bake-action@v4 + uses: docker/bake-action@aefd381cbaa93c62a1e8b02194ae420cc36269d2 # v4.7.0 with: targets: test-${{ matrix.mode }} - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: directory: ${{ env.DESTDIR }}/coverage flags: unit,${{ matrix.mode }},go-${{ matrix.go }} @@ -107,10 +107,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ matrix.go }} - @@ -121,7 +121,7 @@ jobs: - name: Upload coverage if: always() - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: file: ./coverage.txt env_vars: RUNNER_OS @@ -135,10 +135,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Cache Vagrant boxes - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.vagrant.d/boxes key: ${{ runner.os }}-vagrant-${{ hashFiles('hack/Vagrantfile.freebsd13') }} @@ -176,7 +176,7 @@ jobs: - name: Upload coverage if: always() - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: file: ./coverage.txt flags: unit,freebsd From c605ae715b7af25668bd3249a2eedd8c0ca87e10 Mon Sep 17 00:00:00 2001 From: Kieran Mann Date: Fri, 9 Oct 2026 13:51:29 -0700 Subject: [PATCH 3/8] ci: bump docker/bake-action to v5 for buildx >= 0.20 bake-action < v5 refuses to run with the buildx now shipped on ubuntu-latest runners. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit e2fbb01d11b911a227279f31b9c6c853704609a0) --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f061cf4..fb6598c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Run - uses: docker/bake-action@aefd381cbaa93c62a1e8b02194ae420cc36269d2 # v4.7.0 + uses: docker/bake-action@4a9a8d494466d37134e2bfca2d3a8de8fb2681ad # v5.13.0 with: targets: ${{ matrix.target }} @@ -57,7 +57,7 @@ jobs: uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Run - uses: docker/bake-action@aefd381cbaa93c62a1e8b02194ae420cc36269d2 # v4.7.0 + uses: docker/bake-action@4a9a8d494466d37134e2bfca2d3a8de8fb2681ad # v5.13.0 with: targets: ${{ matrix.target }} @@ -82,7 +82,7 @@ jobs: uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Test - uses: docker/bake-action@aefd381cbaa93c62a1e8b02194ae420cc36269d2 # v4.7.0 + uses: docker/bake-action@4a9a8d494466d37134e2bfca2d3a8de8fb2681ad # v5.13.0 with: targets: test-${{ matrix.mode }} - From 983693e0d17f9321170921c33b6e80cc96d80a19 Mon Sep 17 00:00:00 2001 From: Kieran Mann Date: Fri, 9 Oct 2026 14:57:33 -0700 Subject: [PATCH 4/8] ci: log in to Docker Hub before bake to avoid pull rate limits Anonymous pulls of tonistiigi/xx and mvdan/shfmt hit Docker Hub's 429 rate limit on shared runners. Use the EarthBuild org's CI account (vars.DOCKERHUB_USERNAME / secrets.DOCKERHUB_TOKEN); skipped for fork PRs, which don't receive secrets. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb6598c..2f9ab8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,15 @@ jobs: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - + name: Login to Docker Hub + # Authenticated pulls avoid Docker Hub's anonymous rate limit (429). + # Secrets are unavailable to fork PRs, so skip the login there. + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 @@ -52,6 +61,15 @@ jobs: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - + name: Login to Docker Hub + # Authenticated pulls avoid Docker Hub's anonymous rate limit (429). + # Secrets are unavailable to fork PRs, so skip the login there. + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 @@ -77,6 +95,15 @@ jobs: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - + name: Login to Docker Hub + # Authenticated pulls avoid Docker Hub's anonymous rate limit (429). + # Secrets are unavailable to fork PRs, so skip the login there. + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 From 078c86bede4cd86b67932a2d465dccdb2bc695c1 Mon Sep 17 00:00:00 2001 From: Kieran Mann Date: Fri, 9 Oct 2026 14:58:11 -0700 Subject: [PATCH 5/8] ci: run FreeBSD tests in a vmactions VM on ubuntu-latest test-freebsd-amd64 targeted the retired macos-13 runner, so it queued forever and the workflow run never completed. Port upstream tonistiigi/fsutil's approach (vmactions/freebsd-vm on Linux) for the FreeBSD leg, keeping the fork's Go 1.21 and actions pinned by SHA. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 70 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f9ab8e..8dec002 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,50 +156,50 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} test-freebsd-amd64: - runs-on: macos-13 + # macos-13 (and Vagrant/VirtualBox on it) is retired; run FreeBSD in a VM + # on Linux the way upstream tonistiigi/fsutil does. + runs-on: ubuntu-latest env: - VAGRANT_VAGRANTFILE: hack/Vagrantfile.freebsd13 + GO_VERSION: "1.21" steps: + - + name: Prepare + shell: bash + run: | + goVersion=$(curl --fail --silent --show-error "https://go.dev/dl/?mode=json&include=all" | jq -er --arg version "go${GO_VERSION}." '[.[] | select(.stable and (.version | startswith($version)))][0].version') + echo "GO_VERSION=${goVersion#go}" >> "$GITHUB_ENV" - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - name: Cache Vagrant boxes - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + name: Set up FreeBSD + uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc # v1.5.9 with: - path: ~/.vagrant.d/boxes - key: ${{ runner.os }}-vagrant-${{ hashFiles('hack/Vagrantfile.freebsd13') }} - restore-keys: | - ${{ runner.os }}-vagrant- - - - name: Install Vagrant and VirtualBox - run: | - set -x - brew tap hashicorp/tap - brew install hashicorp/tap/hashicorp-vagrant - brew install --cask virtualbox - env: - HOMEBREW_NO_AUTO_UPDATE: 1 - HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 - - - name: Check versions - run: | - set -x - vagrant --version - VBoxManage -v - - - name: Set up vagrant - run: | - vagrant up --no-tty + release: "14.5" + envs: GO_VERSION + custom-shell-name: bsd + prepare: | + set -ex + mkdir -p /var/tmp + chmod 1777 /var/tmp + archive="go${GO_VERSION}.freebsd-amd64.tar.gz" + fetch -o "/var/tmp/$archive" "https://go.dev/dl/$archive" + tar -C /var/tmp -xzf "/var/tmp/$archive" + pw useradd -n fsutil -m -s /bin/sh - name: Test - uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0 - with: - timeout_minutes: 20 - max_attempts: 5 - command: | - vagrant ssh -- "cd /vagrant; go test -buildvcs=false -coverprofile=coverage.txt -covermode=atomic ./..." - vagrant ssh -c "sudo cat /vagrant/coverage.txt" > coverage.txt + run: | + bsd /dev/stdin <<'EOF' + set -ex + chown -R fsutil "$GITHUB_WORKSPACE" + su - fsutil -c ' + set -ex + cd "$1" + export TMPDIR=/var/tmp + export PATH="/var/tmp/go/bin:$PATH" + go test -buildvcs=false -coverprofile=coverage.txt -covermode=atomic ./... + ' sh "$GITHUB_WORKSPACE" + EOF - name: Upload coverage if: always() From 13a2fb626170d3478dd3e95851a6f98e350f8525 Mon Sep 17 00:00:00 2001 From: Kieran Mann Date: Fri, 9 Oct 2026 14:58:11 -0700 Subject: [PATCH 6/8] ci: skip Go 1.21 on macOS where its test binaries cannot load Go 1.21 binaries abort with "dyld: missing LC_UUID load command" on the current macos-latest image before any test runs. Go 1.20 keeps macOS coverage; Windows and Linux still run 1.21. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8dec002..74de998 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,6 +131,11 @@ jobs: go: - "1.21" - "1.20" + exclude: + # Go 1.21 test binaries abort on current macOS runner images + # ("dyld: missing LC_UUID load command"); Go 1.20 still covers macOS. + - os: macos-latest + go: "1.21" steps: - name: Checkout From ef0123f93bea438a6e818167e537462406fa49ed Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 4 Jun 2026 17:26:12 -0700 Subject: [PATCH 7/8] ci: run OS tests with bash Force the test-os workflow step to use bash so a failing go test command is not masked by a later coverage command on Windows PowerShell. Signed-off-by: Tonis Tiigi (cherry picked from commit 818582ba80913f067501e4182f1f2b8da81edb99) Without a shell, windows-latest runs this step in PowerShell, where a failing native command does not stop the script: `go tool cover` succeeding overwrote `go test`'s exit code. Run 37996598366 (jobs 114044153320, 114044153527) logged `--- FAIL: TestHardlinkFilter` yet both Windows jobs concluded success. The other multi-command run steps already stop on failure: the FreeBSD Prepare step sets `shell: bash`, and the FreeBSD Test step runs on ubuntu-latest (default `bash -e`) with `set -ex` inside the VM script. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74de998..0af4d51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,7 @@ jobs: go-version: ${{ matrix.go }} - name: Test + shell: bash run: | go test -coverprofile="coverage.txt" -covermode="atomic" ./... go tool cover -func="coverage.txt" From 0c1f49d76589a75f072bcbb2683fc9c3b4d7f68e Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 4 Jun 2026 17:30:13 -0700 Subject: [PATCH 8/8] test: skip hardlink filter on Windows Windows stat handling does not populate hardlink linknames, so TestHardlinkFilter cannot assert the Unix hardlink normalization behavior there. Signed-off-by: Tonis Tiigi (cherry picked from commit a0b4937f7b24059165ad6cc9e149fe405feb3f5d) Cherry-picked rather than fixed: the failure is pre-existing upstream behaviour, not something the merge or #4 introduced. TestHardlinkFilter arrived with upstream 91a3fc4 (tonistiigi/fsutil#198), and setUnixOpt in stat_windows.go is a no-op, so the inode map that fills Stat.Linkname for hardlinks never runs on Windows. Upstream's own CI masked the failure the same way until tonistiigi/fsutil#259, which added `shell: bash` and this skip together (its bash-only run 26987953480 failed all three Windows test-os jobs). With the bash fix alone, workflow_dispatch probes failed the same way on Windows (Go 1.20 and 1.21) on upstream 91a3fc4 (run 38000549628) and on #4's code e66a226 (run 38000552489), with `foo2: The system cannot find the file specified.` and an empty Linkname where "foo" was expected. Co-Authored-By: Claude Opus 5.5 --- receive_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/receive_test.go b/receive_test.go index 4f3d8e3..4ffff20 100644 --- a/receive_test.go +++ b/receive_test.go @@ -217,6 +217,10 @@ func TestCopySwitchDirToFile(t *testing.T) { } func TestHardlinkFilter(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("hardlink stat linknames are not populated on windows") + } + d, err := tmpDir(changeStream([]string{ "ADD bar file data1", "ADD foo file >bar",