name: CI
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
inputs:
ignore_windows_only:
description: Restrict focused ignore checks to Windows
type: boolean
default: false
ignore_only:
description: Run focused ignore compatibility and performance checks
type: boolean
default: false
storage_only:
description: Run only focused file-backed storage checks
type: boolean
default: false
alternates_only:
description: Run only focused native alternate storage checks
type: boolean
default: false
http_only:
description: Run only focused Windows HTTP regression checks
type: boolean
default: false
retention_only:
description: Run focused retention and repack checks
type: boolean
default: false
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
msrv:
name: MSRV (1.97.1)
needs: changes
if: ${{ needs.changes.outputs.rust == 'true' }}
runs-on: ubuntu-26.04
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- run: rustup toolchain install 1.97.1 --profile minimal
- run: cargo +1.97.1 check --locked --all-targets --all-features
- run: cargo +1.97.1 test --locked --no-default-features --lib
unix:
name: Test (${{ matrix.os }})
needs: changes
if: ${{ needs.changes.outputs.rust == 'true' && !inputs.ignore_only && !inputs.http_only && !inputs.alternates_only && !inputs.storage_only && !inputs.retention_only }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-26.04, macos-14]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Toolchain and environment
run: |
rustup toolchain install stable --profile minimal --component clippy
rustc +stable -Vv
git --version
uname -a
if [ "$RUNNER_OS" = Linux ]; then
cat /etc/os-release
df -T . "$(python3 -c 'import tempfile; print(tempfile.gettempdir())')"
else
sw_vers
diskutil info "$(df . | tail -1 | awk '{print $1}')"
diskutil info "$(df "$(python3 -c 'import tempfile; print(tempfile.gettempdir())')" | tail -1 | awk '{print $1}')"
fi
- run: cargo +stable test --locked --all-features -- --test-threads=1
- name: Core-only runtime without tracing
run: >-
cargo +stable test --locked --no-default-features --no-fail-fast
--lib --test layout_shallow --test worktree_create --test worktree_admin --test object_ids --test sha256 --test commits --test decoding --test packs --test index --test colocation --test references --test references_portable --test retention --test retention_integrity --test retention_survivors --test retention_repack_contracts
- name: Core-only library
run: cargo +stable check --locked --lib --no-default-features
- name: HTTP-only library
run: cargo +stable check --locked --lib --no-default-features --features http
- name: Tracing without network dependencies
run: cargo +stable test --locked --no-default-features --features tracing --test tracing
- name: Tracing HTTP lifecycle
run: cargo +stable test --locked --no-default-features --features tracing,http --test tracing
- name: Tracing SSH-only library
run: cargo +stable check --locked --lib --no-default-features --features tracing,ssh
- name: SSH-only library
run: cargo +stable check --locked --lib --no-default-features --features ssh
- name: HTTP-only runtime without tracing
run: cargo +stable test --locked --no-default-features --features http --no-fail-fast --test http --test http_portable
- name: SSH-only runtime without tracing
run: cargo +stable test --locked --no-default-features --features ssh --test ssh
- run: cargo +stable clippy --locked --all-features --all-targets -- -D warnings
- run: cargo +stable run --locked --example fetch_local
- run: cargo +stable run --locked --example push_local
- run: cargo +stable run --locked --features http --example http_local
- run: cargo +stable run --locked --features ssh --example ssh_local
- run: cargo +stable run --locked --example content_diff
- run: cargo +stable run --locked --example index
- run: cargo +stable run --locked --example write_pack
- run: cargo +stable run --locked --example init_repository
- run: cargo +stable run --locked --features tracing --example tracing
- run: cargo +stable run --locked --example reference_transaction
- run: cargo +stable run --locked --example status
- run: cargo +stable run --locked --example checkout
- name: Rustdoc
env:
RUSTDOCFLAGS: -D warnings
run: cargo +stable doc --locked --all-features --no-deps --document-private-items
windows:
name: Test (windows-2022)
needs: changes
if: ${{ needs.changes.outputs.rust == 'true' && !inputs.ignore_only && !inputs.http_only && !inputs.alternates_only && !inputs.storage_only && !inputs.retention_only }}
runs-on: windows-2022
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Toolchain and environment
run: |
rustup toolchain install stable --profile minimal --component clippy
rustc +stable -Vv
git --version
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, BuildNumber | ConvertTo-Json -Compress
Get-Volume | Where-Object DriveLetter | Select-Object DriveLetter, FileSystem, FileSystemLabel | ConvertTo-Json -Compress
Write-Output "Temporary directory: $env:TEMP"
- name: Native local transfer
run: cargo +stable test --locked --no-default-features --test local_native --test transfer_reuse
- name: Core-only library
run: cargo +stable check --locked --lib --no-default-features
- name: HTTP-only library
run: cargo +stable check --locked --lib --no-default-features --features http
- name: Tracing without network dependencies
run: cargo +stable test --locked --no-default-features --features tracing --test tracing
- name: Tracing HTTP lifecycle
run: cargo +stable test --locked --no-default-features --features tracing,http --test tracing
- name: Tracing SSH-only library
run: cargo +stable check --locked --lib --no-default-features --features tracing,ssh
- name: SSH-only library
run: cargo +stable check --locked --lib --no-default-features --features ssh
- run: cargo +stable clippy --locked --all-features --all-targets -- -D warnings
- run: cargo +stable test --locked --all-features --lib
- name: Core-only units without tracing
run: cargo +stable test --locked --no-default-features --lib
- run: cargo +stable test --locked --all-features --doc
- name: Portable Git interoperability in both object formats
run: >-
cargo +stable test --locked --no-default-features --no-fail-fast
--test object_ids --test sha256 --test blobs --test trees --test commits --test decoding --test discovery --test tags --test packs
--test history --test repositories --test layout_shallow --test worktree_create --test worktree_admin --test config_resolution --test config_edit --test remotes --test tree_compare --test content_diff --test index --test colocation --test status_portable --test checkout_portable --test references --test references_portable --test retention
- name: Portable HTTP runtime
if: ${{ !cancelled() }}
run: cargo +stable test --locked --no-default-features --features http --no-fail-fast --test http --test http_portable
- run: cargo +stable run --locked --example content_diff
- run: cargo +stable run --locked --example index
- run: cargo +stable run --locked --example write_pack
- run: cargo +stable run --locked --example init_repository
- run: cargo +stable run --locked --features tracing --example tracing
- name: Public Rustdoc
env:
RUSTDOCFLAGS: -D warnings
run: cargo +stable doc --locked --all-features --no-deps
windows-http:
name: HTTPS (windows-2022)
if: ${{ inputs.http_only }}
runs-on: windows-2022
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Toolchain and environment
run: |
rustup toolchain install stable --profile minimal --component clippy
rustc +stable -Vv
git --version
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, BuildNumber | ConvertTo-Json -Compress
Get-Volume | Where-Object DriveLetter | Select-Object DriveLetter, FileSystem, FileSystemLabel | ConvertTo-Json -Compress
Write-Output "Temporary directory: $env:TEMP"
- name: HTTP runtime regression
run: cargo +stable test --locked --no-default-features --features http --no-fail-fast --test http --test http_portable -- --show-output
- name: HTTP test Clippy
run: cargo +stable clippy --locked --no-default-features --features http --test http --test http_portable -- -D warnings
alternates:
name: Alternates (${{ matrix.os }})
if: ${{ inputs.alternates_only }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-26.04, macos-14, windows-2022]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Toolchain and environment
run: |
rustup toolchain install stable --profile minimal --component clippy
rustc +stable -Vv
git --version
python -c "import platform,tempfile; print(platform.platform()); print(tempfile.gettempdir())"
- name: Both-format alternate and repository contracts
run: cargo +stable test --locked --no-default-features --test alternates --test repositories --test packs
- name: Parser units and tracing build
run: cargo +stable test --locked --all-features --lib objects::alternates
- name: Scoped Clippy
run: cargo +stable clippy --locked --all-features --lib --test alternates --test repositories --test packs -- -D warnings
- name: Original Git observations
run: python tests/fixtures/alternates/observe.py
storage:
name: Storage (${{ matrix.os }})
if: ${{ inputs.storage_only }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-26.04, macos-14, windows-2022]
runs-on: ${{ matrix.os }}
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Environment
run: |
rustup toolchain install stable --profile minimal --component clippy
rustc +stable -Vv
git --version
python -c "import platform,tempfile; print(platform.platform()); print(tempfile.gettempdir())"
- name: Storage behavior and original decoder corpus
run: cargo +stable test --locked --all-features --lib --test packs --test alternates --test repositories --test refresh
- name: Scoped Clippy
run: cargo +stable clippy --locked --all-features --lib --test packs --test alternates --test repositories --test refresh --example storage_resources --bench packs -- -D warnings
- name: Refresh after received-pack installation
if: runner.os != 'Windows'
run: cargo +stable test --locked --all-features --test fetch imports_server_deltas_and_all_object_kinds
- name: Large original Git fixture
run: python tests/fixtures/file-storage/generate.py large-store sha1
- name: Build isolated resource probe
run: cargo +stable build --locked --release --example storage_resources
- name: Measure reader resources and cancellation
run: python tests/fixtures/file-storage/measure.py large-store
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a if: always()
with:
name: storage-${{ matrix.os }}
path: |
large-store/workload.json
storage-measurements.json
ignore:
name: Ignore (${{ matrix.os }})
if: ${{ inputs.ignore_only }}
strategy:
fail-fast: false
matrix:
os: ${{ inputs.ignore_windows_only && fromJSON('["windows-2022"]') || fromJSON('["ubuntu-26.04", "macos-14", "windows-2022"]') }}
runs-on: ${{ matrix.os }}
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Environment
run: |
rustup toolchain install stable --profile minimal --component clippy
rustc +stable -Vv
git --version
python -c "import platform,tempfile; print(platform.platform()); print(tempfile.gettempdir())"
- name: Unit and public compatibility cases
run: "cargo +stable test --locked --no-default-features --lib --test ignore ignore::"
- name: Both-format Git comparisons
run: cargo +stable test --locked --no-default-features --test ignore
- name: Tracing feature composition
run: "cargo +stable test --locked --no-default-features --features tracing --lib --test ignore ignore::"
- name: Clippy
run: cargo +stable clippy --locked --no-default-features --lib --test ignore --bench ignore --example ignore_batch -- -D warnings
- name: Build batch adapter
run: cargo +stable build --locked --release --example ignore_batch
- name: Independent adversarial observations
run: python tests/fixtures/ignore/observe.py > ignore-observations.json
- name: Large source resources
run: python tests/fixtures/ignore/resources.py > ignore-resources.json
- name: Representative matched batches
run: cargo +stable bench --locked --bench ignore -- --noplot
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a if: always()
with:
name: ignore-${{ matrix.os }}
path: |
ignore-observations.json
ignore-resources.json
target/criterion
retention:
name: Retention (${{ matrix.os }})
if: ${{ inputs.retention_only }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-26.04, macos-14, windows-2022]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- name: Toolchain and Git
run: |
rustup toolchain install stable --profile minimal --component clippy
rustc +stable -Vv
git --version
python -c "import platform,tempfile; print(platform.platform()); print(tempfile.gettempdir())"
- name: Retention and repack cases
run: cargo +stable test --locked --no-default-features --test retention --test retention_integrity --test retention_survivors --test retention_repack_contracts
- name: Maintenance fault and race cases
run: |
cargo +stable test --locked --no-default-features --lib 'retention::'
cargo +stable test --locked --no-default-features --lib 'refs::reftable::stack_tests::expiry_'
- name: Retention API and benchmark lint
run: cargo +stable clippy --locked --no-default-features --lib --test retention --test retention_integrity --test retention_survivors --test retention_repack_contracts --bench retention -- -D warnings
changes:
name: Changes
runs-on: ubuntu-26.04
timeout-minutes: 5
outputs:
rust: ${{ steps.paths.outputs.rust }}
docs: ${{ steps.paths.outputs.docs }}
index: ${{ steps.paths.outputs.index }}
references: ${{ steps.paths.outputs.references }}
security: ${{ steps.paths.outputs.security }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
fetch-depth: 0
filter: blob:none
persist-credentials: false
- name: Select checks for changed paths
id: paths
env:
EVENT: ${{ github.event_name }}
BEFORE: ${{ github.event.before }}
run: |
# A manual run keeps the Rust jobs, whose inputs select focused checks, and skips the rest.
if [ "$EVENT" = workflow_dispatch ]; then
printf '%s\n' rust=true docs=false index=false references=false security=false >> "$GITHUB_OUTPUT"
exit 0
fi
# A pull request checks out its merge commit, whose first parent is the base branch.
if [ "$EVENT" = pull_request ]; then
changed=$(git diff --name-only HEAD^1 HEAD)
elif git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then
changed=$(git diff --name-only "$BEFORE" HEAD)
else
changed=
all=true
fi
printf '%s\n' "$changed"
# Documentation-only files, including everything under docs/.
prose='(\.md$|^docs/|^LICENSE|^\.vale\.ini$|^\.config/rumdl\.toml$|^\.config/vale/)'
if [ -n "${all:-}" ] || grep -qvE "$prose" <<<"$changed"; then
echo rust=true >> "$GITHUB_OUTPUT"
else
echo rust=false >> "$GITHUB_OUTPUT"
fi
route() {
if [ -n "${all:-}" ] || grep -qE "$2" <<<"$changed"; then
echo "$1=true" >> "$GITHUB_OUTPUT"
else
echo "$1=false" >> "$GITHUB_OUTPUT"
fi
}
route index '^(src/|tests/(index|colocation)\.rs$|\.github/workflows/(index|validation)\.yml$)'
route references '^(src/|tests/(references_portable|worktree_create|worktree_admin)\.rs$|\.github/workflows/(references|validation)\.yml$)'
route docs "$prose|^(justfile|\.github/workflows/validation\.yml)\$"
route security '^(\.github/|Cargo\.(toml|lock)$|deny\.toml$)'
docs:
name: Docs
needs: changes
if: ${{ needs.changes.outputs.docs == 'true' }}
runs-on: ubuntu-26.04
timeout-minutes: 10
env:
VALE_VERSION: 3.24.0
VALE_SHA256: 867534ddb678abca7f214bc4706ead0922bd5252ffe8035fc227013f66c8b214
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 - name: Install just and rumdl
run: uv tool install "rust-just==1.58.0" && uv tool install "rumdl==0.2.77"
- name: Install Vale
run: |
archive="vale_${VALE_VERSION}_Linux_64-bit.tar.gz"
curl -fsSLO "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/${archive}"
echo "${VALE_SHA256} ${archive}" | sha256sum -c -
mkdir -p "$HOME/.local/bin"
tar -xzf "$archive" -C "$HOME/.local/bin" vale
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Markdown format and prose
run: just docs-check
index:
name: Index
needs: changes
if: ${{ needs.changes.outputs.index == 'true' }}
uses: $/.github/workflows/index.yml
references:
name: References
needs: changes
if: ${{ needs.changes.outputs.references == 'true' }}
uses: $/.github/workflows/references.yml
security:
name: Security
needs: changes
if: ${{ needs.changes.outputs.security == 'true' }}
uses: $/.github/workflows/security.yml
required:
name: required
if: ${{ always() }}
needs:
- msrv
- unix
- windows
- windows-http
- alternates
- storage
- ignore
- retention
- changes
- docs
- index
- references
- security
runs-on: ubuntu-26.04
timeout-minutes: 5
steps:
- name: Require success or skip from every needed job
env:
NEEDS: ${{ toJSON(needs) }}
run: |
jq -r 'to_entries[] | "\(.key): \(.value.result)"' <<<"$NEEDS"
jq -e 'all(.[]; .result == "success" or .result == "skipped")' <<<"$NEEDS" > /dev/null