create src

This commit is contained in:
awfixer
2026-03-11 02:04:19 -07:00
commit 52f7a22bf2
2595 changed files with 402870 additions and 0 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: awfixer

26
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Report undesirable behaviour 🐛
description: Create an issue to help us improve
body:
- type: markdown
attributes:
value: Thanks for contributing by creating an issue! ❤️
- type: textarea
attributes:
label: Current behavior 😯
description: Describe what happens instead of the expected behavior.
- type: textarea
attributes:
label: Expected behavior 🤔
description: Describe what should happen.
- type: textarea
attributes:
label: Git behavior
description: Describe what Git does under similar circumstances.
- type: textarea
attributes:
label: Steps to reproduce 🕹
description: Describe how we can reproduce the behaviour.
placeholder: |
1.
2.
3. …

View File

@@ -0,0 +1,18 @@
name: Feature request 💄
description: Suggest an improvement
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: Thanks for contributing by creating an issue! ❤️
- type: textarea
attributes:
label: Summary 💡
description: Describe how it should work.
- type: textarea
attributes:
label: Motivation 🔦
description: >
What are you trying to accomplish?
How has the lack of this feature affected you?
Are there other tools or libraries already showing how (_parts of_) it should work?

142
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,142 @@
# Copilot Instructions for Gitoxide
This repository contains `gitoxide` - a pure Rust implementation of Git. This document provides guidance for GitHub Copilot when working with this codebase.
## Project Overview
- **Language**: Rust (MSRV documented in src/Cargo.toml)
- **Structure**: Cargo workspace with multiple crates (src-\*, gitoxide-core, etc.)
- **Main crates**: `src` (library entrypoint), `gitoxide` binary (CLI tools: `src` and `ein`)
- **Purpose**: Provide a high-performance, safe Git implementation with both library and CLI interfaces
## Development Practices
### Test-First Development
- Protect against regression and make implementing features easy
- Keep it practical - the Rust compiler handles mundane things
- Use git itself as reference implementation; run same tests against git where feasible
- Never use `.unwrap()` in production code, avoid it in tests in favor of `.expect()` or `?`. Use `src_testtools::Result` most of the time.
- Use `.expect("why")` with context explaining why expectations should hold, but only if it's relevant to the test.
### Error Handling
- Handle all errors, never `unwrap()`
- Provide error chains making it easy to understand what went wrong
- Binaries may use `anyhow::Error` exhaustively (user-facing errors)
#### `src-error` (preferred for plumbing crates)
Plumbing crates are migrating from `thiserror` enums to `src-error`. Check whether a crate already
uses `src-error` (look at its `Cargo.toml`); if it does, follow the patterns below. If it still uses
`thiserror`, keep using `thiserror` for consistency within that crate.
- **Error type alias**: `pub type Error = src_error::Exn<src_error::Message>;`
- **Static messages**: `src_error::message("something failed")`
- **Formatted messages**: `src_error::message!("failed to read {path}")`
- **Wrapping callee errors with context**: `.or_raise(|| message("context about what failed"))?`
- **Standalone error (no callee)**: `Err(message("something went wrong").raise())`
- **Wrapping an `impl Error` with context**: `err.and_raise(message("context"))`
- **Closure/callback bounds**: use `Result<T, Exn>` (bare), not `Exn<Message>`;
inside the function, convert with `.or_raise(|| message("..."))?`;
inside the closure, convert typed to bare with `.or_erased()`
- **`Exn<E>` does NOT implement `std::error::Error`** — this is by design.
- To convert: use `.into_error()` to get `src_error::Error` (which does implement `std::error::Error`)
- Example: `std::io::Error::other(exn.into_error())`
- **In tests** returning `src_testtools::Result` (= `Result<(), Box<dyn Error>>`), `Exn` can't be used
with `?` directly — use `.map_err(|e| e.into_error())?`
- **Common imports**: `use src_error::{message, ErrorExt, ResultExt};`
- See `src-error/src/lib.rs` module docs for a full migration guide from `thiserror`
### Commit Messages
Follow "purposeful conventional commits" style:
- Use conventional commit prefixes ONLY if message should appear in changelog
- Breaking changes MUST use suffix `!`: `change!:`, `remove!:`, `rename!:`
- Features/fixes visible to users: `feat:`, `fix:`
- Refactors/chores: no prefix (don't affect users)
- Examples:
- `feat: add Repository::foo() to do great things. (#234)`
- `fix: don't panic when calling foo() in a bare repository. (#456)`
- `change!: rename Foo to Bar. (#123)`
### Code Style
- Follow existing patterns in the codebase
- No `.unwrap()` - use `.expect("context")` if you are sure this can't fail.
- Prefer references in plumbing crates to avoid expensive clones
- Use `src_features::threading::*` for interior mutability primitives
### Path Handling
- Paths are byte-oriented in git (even on Windows via MSYS2 abstraction)
- Use `src::path::*` utilities to convert git paths (`BString`) to `OsStr`/`Path` or use custom types
## Building and Testing
### Quick Commands
- `just test` - Run all tests, clippy, journey tests, and try building docs
- `just check` - Build all code in suitable configurations
- `just clippy` - Run clippy on all crates
- `cargo test` - Run unit tests only
### Build Variants
- `cargo build --release` - Default build (big but pretty, ~2.5min)
- `cargo build --release --no-default-features --features lean` - Lean build (~1.5min)
- `cargo build --release --no-default-features --features small` - Minimal deps (~46s)
### Test Best Practices
- Run tests before making changes to understand existing issues
- Use `src_TEST_IGNORE_ARCHIVES=1` when testing on macOS/Windows
- Journey tests validate CLI behavior end-to-end
## Architecture Decisions
### Plumbing vs Porcelain
- **Plumbing crates**: Low-level, take references, expose mutable parts as arguments
- **Porcelain (src)**: High-level, convenient, may clone Repository for user convenience
- Platforms: cheap to create, keep reference to Repository
- Caches: more expensive, clone `Repository` or free of lifetimes
### Options vs Context
- Use `Options` for branching behavior configuration (can be defaulted)
- Use `Context` for data required for operation (cannot be defaulted)
## Crate Organization
### Common Crates
- `src`: Main library entrypoint (porcelain)
- `src-object`, `src-ref`, `src-config`: Core git data structures
- `src-odb`, `src-pack`: Object database and pack handling
- `src-diff`, `src-merge`, `src-status`: Operations
- `gitoxide-core`: Shared CLI functionality
## Documentation
- High-level docs: README.md, CONTRIBUTING.md, DEVELOPMENT.md
- Crate status: crate-status.md
- Stability guide: STABILITY.md
- Always update docs if directly related to code changes
## CI and Releases
- Ubuntu-latest git version is the compatibility target
- `cargo smart-release` for releases (driven by commit messages)
- Split breaking changes into separate commits per affected crate if one commit-message wouldn't be suitable for all changed crates.
- First commit: breaking change only; second commit: adaptations
## When Suggesting Changes
1. Understand the plumbing vs porcelain distinction
2. Check existing patterns in similar crates
3. Follow error handling conventions strictly
4. Ensure changes work with feature flags (small, lean, max, max-pure)
5. Consider impact on both library and CLI users
6. Test against real git repositories when possible

50
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
version: 2
updates:
- package-ecosystem: cargo
directory: '/'
schedule:
interval: monthly
commit-message:
# Avoid non-"purposeful" prefix due to Dependabot misdetecting style (see `DEVELOPMENT.md`).
prefix: ''
allow:
- dependency-type: all
ignore:
# Keep `expectrl` at 0.7.* for now. See https://github.com/GitoxideLabs/gitoxide/pull/2200.
- dependency-name: expectrl
update-types:
- 'version-update:semver-major'
- 'version-update:semver-minor'
# Some `getrandom` dependencies are at 0.2.*. Keep them there for now. (Some are at 0.3.*.
# This allows them to remain there but keeps them from going to a future 0.4.*.) This is
# hopefully temporary. See https://github.com/GitoxideLabs/gitoxide/pull/2093.
- dependency-name: getrandom
update-types:
- 'version-update:semver-major'
- 'version-update:semver-minor'
# Keep `imara-diff` at 0.1.* for now. See https://github.com/GitoxideLabs/gitoxide/pull/2068.
- dependency-name: imara-diff
update-types:
- 'version-update:semver-major'
- 'version-update:semver-minor'
groups:
cargo:
patterns: ['*']
exclude-patterns:
- expectrl
- imara-diff
cooldown:
default-days: 7
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: monthly
commit-message:
# Avoid non-"purposeful" prefix due to Dependabot misdetecting style (see `DEVELOPMENT.md`).
prefix: ''
groups:
github-actions:
patterns: ['*']
cooldown:
default-days: 7

701
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,701 @@
name: ci
on:
push:
branches:
- main
- 'run-ci/**'
- '**/run-ci/**'
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CLICOLOR: '1'
jobs:
msrv:
name: cargo check MSRV
strategy:
matrix:
os:
- windows-2025
- ubuntu-latest
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash # Use `bash` even in the Windows job.
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
- name: Read the MSRV
run: |
msrv="$(just msrv)"
tee -a "$GITHUB_ENV" <<<"MSRV=$msrv"
- name: Set up MSRV and nightly toolchains
run: |
rustup toolchain install "$MSRV" nightly --profile minimal --no-self-update
- name: Downgrade locked dependencies to lowest allowed versions
run: |
# TODO(msrv): Use `cargo update --minimal-versions` when `--minimal-versions` is
# available. See https://github.com/rust-lang/cargo/issues/5657 on that feature,
# and https://github.com/GitoxideLabs/gitoxide/issues/1119 on its use here.
cargo +nightly update -Zminimal-versions
- name: Upgrade `errno` just enough for `winapi` to build on modern Rust
if: startsWith(matrix.os, 'windows')
run: |
# `src-tempfile` depends directly on a recent version of `signal-hook-registry`. Since
# version 1.4.8, `signal-hook-registry` depends on `errno`, and it allows a wide version
# range. Downgrading dependencies to minimal versions in the previous step gives `errno`
# 0.2.0; on Windows, this depends on `winapi` 0.2. Until version 0.3, `winapi` relied on
# implicit wraparound in unsigned overflow at compile time, which modern Rust prohibits
# (https://github.com/rust-lang/rust/issues/49765). To work around the problem of having
# downgraded `errno` too far to build on Windows, we bump it back up just a bit. (These
# steps can't easily be done together in a single `cargo update` command, because there
# is no `errno@0.2` to upgrade until after the downgrade done in the previous step.)
cargo +nightly update -Zminimal-versions -p errno@0.2 --precise 0.2.4
- name: Run some `cargo build` commands on `src`
run: just check-rust-version "$MSRV"
msrv-badge:
name: Check MSRV badge
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
- name: Ensure we start out clean
run: git diff --exit-code
- name: Regenerate the MSRV badge
run: just msrv-badge
- name: Check for changes
run: git diff --exit-code
pure-rust-build:
runs-on: ubuntu-latest
container: debian:stable-slim
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Prerequisites
run: |
prerequisites=(
ca-certificates
curl
gcc # rustc calls gcc to invoke the linker.
libc-dev # rustc, in the toolchain we are using, dynamically links to the system libc.
)
apt-get update
apt-get install --no-install-recommends -y -- "${prerequisites[@]}"
shell: bash # This step needs `bash`, and the default in container jobs is `sh`.
- name: Verify that we are in an environment with limited dev tools
run: |
set -x
for package in cmake g++ libssl-dev make pkgconf pkg-config; do
if dpkg-query --status -- "$package"; then
exit 1
fi
done
for cmd in cmake g++ make pkgconf pkg-config; do
if command -v -- "$cmd"; then
exit 1
fi
done
- name: Install Rust via Rustup
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
sh -s -- -y --profile minimal
- name: Add Rust tools to path
run: echo "PATH=$HOME/.cargo/bin:$PATH" >> "$GITHUB_ENV"
- name: Generate dependency tree
run: cargo tree --locked --no-default-features --features max-pure > tree.txt
- name: Scan for dependencies that build C or C++ code
run: |
pattern='.*\b(-sys|cc|cmake|pkg-config|vcpkg)\b.*'
! GREP_COLORS='ms=30;48;5;214' grep --color=always -Ex -C 1000000 -e "$pattern" tree.txt
continue-on-error: true
- name: Check for unrecognized *-sys dependencies
run: |
! grep -qP '(?<!\b(linux-raw|aws-lc))-sys\b' tree.txt
- name: Wrap cc1 (and cc1plus if present) to record calls
run: |
set -o noclobber # Catch any collisions with existing entries in /usr/local.
# Define the wrapper script for a compiler driver (for cc1 or cc1plus). This wrapper
# records calls, then delegates to the executable it wraps. When recording calls, writes
# to the log are synchronized so fragments of separate log entries aren't interleaved,
# even in concurrent runs. This wrapper knows what executable it is wrapping because,
# when deployed, this wrapper (or a symlink) replaces that executable, which will itself
# have been moved aside by being renamed with a `.orig` suffix, so this can call it. (The
# lockfile name GUID is just to avoid unintentional collision with unrelated lockfiles.)
cat >/usr/local/bin/wrapper1 <<'EOF'
#!/bin/sh
set -e
printf '%s\n' "$0 $*" |
flock /run/lock/wrapper1.fbd136bd-9b1b-448d-84a9-e18be53ae63c.lock \
tee -a -- /var/log/wrapper1.log ~/display >/dev/null # We'll link ~/display later.
exec "$0.orig" "$@"
EOF
# Define the script that performs the wrapping. This script shall be run once for each
# executable to be wrapped, renaming it with a `.orig` suffix and replacing it with a
# symlink to the wrapper script, defined above.
cat >/usr/local/bin/wrap1 <<'EOF'
#!/bin/sh
set -e
dir="$(dirname -- "$1")"
base="$(basename -- "$1")"
cd -- "$dir"
mv -- "$base" "$base.orig"
ln -s -- /usr/local/bin/wrapper1 "$base"
EOF
# Define a helper file that, when sourced, wires up the `~/display` symlink `wrapper1`
# uses to report calls as GitHub Actions step output (in addition to writing them to a
# log file). This is needed because stdout and stderr are both redirected elsewhere when
# the wrapper actually runs, and `/dev/tty` is not usable. This must be sourced in the
# same step as the `cargo` command that causes wrapped executables to be run, because
# different steps write to different pipe objects. (This also needs the shell that
# sourced it to remain running. But that is not the cause of the underlying limitation.)
cat >/usr/local/bin/set-display.sh <<'EOF'
ln -s -- "/proc/$$/fd/1" ~/display
EOF
chmod +x /usr/local/bin/wrapper1 /usr/local/bin/wrap1
mkdir /run/lock/wrapper1.fbd136bd-9b1b-448d-84a9-e18be53ae63c.lock
find /usr/lib/gcc \( -name cc1 -o -name cc1plus \) \
-print -exec /usr/local/bin/wrap1 {} \;
- name: Build max-pure with limited dev tools and log cc1
run: |
. /usr/local/bin/set-display.sh
cargo install --debug --locked --no-default-features --features max-pure --path .
- name: Show logged C and C++ compilations (should be none)
run: |
! cat /var/log/wrapper1.log
continue-on-error: true
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
run: |
rustup update stable
rustup default stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Setup dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends liblzma-dev
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
- uses: taiki-e/install-action@cc33365ec7e3350bc47bf935f247582cc6f68344 # v2.65.12
with:
tool: nextest
- name: test
env:
src_TEST_IGNORE_ARCHIVES: '1'
run: just ci-test
test-journey:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
run: |
rustup update stable
rustup default stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
- name: Run journey tests
run: just ci-journey-tests
test-fast:
strategy:
matrix:
os:
- windows-latest
- windows-11-arm
- macos-latest
- ubuntu-latest
- ubuntu-24.04-arm
include:
- test-args: ''
- os: windows-11-arm
test-args: '--skip fuzzed_timeout --skip performance --skip speed'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
shell: bash # Use `bash` on all OSes, for `set -e` behavior.
run: |
rustup update stable
rustup default stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: cargo check default features
if: startsWith(matrix.os, 'windows')
run: cargo check --workspace --bins --examples
- uses: taiki-e/install-action@cc33365ec7e3350bc47bf935f247582cc6f68344 # v2.65.12
with:
tool: nextest
- name: Test (nextest)
env:
src_TEST_CREATE_ARCHIVES_EVEN_ON_CI: '1'
# We deliberately let this step use different shells on different OSes, each the runner
# default: `bash` on Ubuntu and macOS, but `pwsh` on Windows. `bash` on Windows runners
# would use a modified ("Git Bash") environment that is in some ways nicer for our tests,
# due to being more in line with some Unix-oriented assumptions. So we need to verify that
# the test suite is compatible with being run even outside that environment, including that
# `src-testtools` is still able to run fixture scripts with the `bash` shell as intended.
# `src-error` is excluded because it gets compiled with the wrong feature toggles here.
# It's tested individually.
run: | # zizmor: ignore[template-injection]
cargo nextest run --workspace --no-fail-fast --exclude src-error -- ${{ matrix.test-args }}
- name: Check that tracked archives are up to date
run: |
# If this fails, the fix is usually to commit a regenerated archive.
git diff --exit-code
- name: Remove Git for Windows directories from PATH
if: startsWith(matrix.os, 'windows')
run: |
$prefix = 'C:\Program Files\Git'
$filtered = ($Env:PATH -split ';' | Where-Object { $_ -notlike "$prefix\*" }) -join ';'
Add-Content -Value "PATH=$filtered" -Path $Env:GITHUB_ENV
- name: Check that `git` is no longer found
if: startsWith(matrix.os, 'windows')
run: |
$git = Get-Command git -ErrorAction SilentlyContinue
if ($null -eq $git) { exit 0 } else { exit 1 }
- name: Check that `EXEPATH` is unset
if: startsWith(matrix.os, 'windows')
run: if ($null -eq $Env:EXEPATH) { exit 0 } else { exit 1 }
- name: Retest src-path without `git` in `PATH` (nextest)
if: startsWith(matrix.os, 'windows')
env:
TEST_ARGS: ${{ matrix.test-args }}
run: |
cargo nextest run -p src-path --no-fail-fast -- (-split $Env:TEST_ARGS)
test-fixtures-windows:
strategy:
matrix:
os:
- windows-latest
- windows-11-arm
include:
- test-args: ''
- os: windows-11-arm
test-args: '--skip fuzzed-timeout --skip performance --skip speed'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
shell: bash # Use `bash` (not `pwsh`), for `set -e` behavior.
run: |
rustup update stable
rustup default stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@cc33365ec7e3350bc47bf935f247582cc6f68344 # v2.65.12
with:
tool: nextest
- name: Test (nextest)
id: nextest
env:
src_TEST_IGNORE_ARCHIVES: '1'
TEST_ARGS: ${{ matrix.test-args }}
run: |
cargo nextest --profile=with-xml run --workspace --no-fail-fast --exclude src-error -- `
(-split $Env:TEST_ARGS)
continue-on-error: true
- name: Check for errors
run: |
[xml]$junit_xml = Get-Content -Path 'target/nextest/with-xml/junit.xml'
if ($junit_xml.testsuites.errors -ne 0) { exit 1 }
- name: Collect actual failures
run: |
[xml]$junit_xml = Get-Content -Path 'target/nextest/with-xml/junit.xml'
$actual_failures = $junit_xml.SelectNodes("//testcase[failure]") |
ForEach-Object { "$($_.classname) $($_.name)" } |
Sort-Object
Write-Output $actual_failures
Set-Content -Path 'actual-failures.txt' -Value $actual_failures
- name: Compare expected and actual failures
run: |
# Fail on any differences, even unexpectedly passing tests, so they can be investigated.
git --no-pager -c diff.color.old='magenta bold' -c diff.color.new='blue bold' `
diff --no-index --exit-code --unified=1000000 --color=always -- `
etc/test-fixtures-windows-expected-failures-see-issue-1358.txt actual-failures.txt
test-32bit:
strategy:
matrix:
container-architecture: [ i386, arm32v7 ]
include:
- container-architecture: i386
runner-architecture: amd64
runner-os: ubuntu-latest
host-triple: i686-unknown-linux-gnu
- container-architecture: arm32v7
runner-architecture: arm64
runner-os: ubuntu-24.04-arm
host-triple: armv7-unknown-linux-gnueabihf
runs-on: ${{ matrix.runner-os }}
container: ${{ matrix.container-architecture }}/debian:bookworm-slim
steps:
- name: Prerequisites
env:
RUNNER_ARCHITECTURE: ${{ matrix.runner-architecture }}
run: |
prerequisites=(
build-essential
ca-certificates
cmake
curl
git
jq
libssl-dev
"libstdc++6:$RUNNER_ARCHITECTURE" # To support external 64-bit Node.js for actions.
pkgconf
python3-minimal
)
dpkg --add-architecture "$RUNNER_ARCHITECTURE"
apt-get update
apt-get install --no-install-recommends -y -- "${prerequisites[@]}"
shell: bash # This step needs `bash`, and the default in container jobs is `sh`.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust via Rustup
env:
HOST_TRIPLE: ${{ matrix.host-triple }}
run: |
# Specify toolchain to avoid possible misdetection based on the 64-bit running kernel.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
sh -s -- -y --default-host "$HOST_TRIPLE" --profile minimal
- name: Add Rust tools to path
run: echo "PATH=$HOME/.cargo/bin:$PATH" >> "$GITHUB_ENV"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@cc33365ec7e3350bc47bf935f247582cc6f68344 # v2.65.12
with:
tool: nextest
- name: Make `system` scope nonempty for "GitInstallation" tests
run: git config --system gitoxide.imaginary.arbitraryVariable arbitraryValue
- name: Test (nextest)
env:
src_TEST_IGNORE_ARCHIVES: '1'
run: cargo nextest run --workspace --no-fail-fast --exclude src-error
test-32bit-windows-size-doc:
runs-on: windows-latest
env:
TARGET: i686-pc-windows-msvc
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
shell: bash # Use `bash` (not `pwsh`), for `$` expansion and `set -e` behavior.
run: |
rustup update stable
rustup default stable
rustup target add "$TARGET"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@cc33365ec7e3350bc47bf935f247582cc6f68344 # v2.65.12
with:
tool: nextest
- name: Test data structure sizes (nextest)
run: cargo nextest run --target $Env:TARGET --workspace --no-fail-fast size
- name: Doctest
run: cargo test --workspace --doc --no-fail-fast
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
run: |
rustup update stable
rustup default stable
rustup component add clippy rustfmt
- name: Install cargo machete
run: cargo install cargo-machete --version 0.9.1 --locked
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
- name: Run cargo machete
run: cargo machete
- name: Run cargo clippy
run: just clippy -D warnings -A unknown-lints --no-deps
- name: Run cargo doc
run: just doc
- name: Run cargo fmt
run: cargo fmt --all -- --check
- name: Install cargo diet
env:
CARGO_DIET_TAG: v1.2.7
run: |
curl -LSfs "https://raw.githubusercontent.com/the-lean-crate/cargo-diet/refs/tags/$CARGO_DIET_TAG/ci/install.sh" |
sh -s -- --git the-lean-crate/cargo-diet --target x86_64-unknown-linux-musl --tag "$CARGO_DIET_TAG"
- name: Run cargo diet
run: just check-size
# Let's not fail CI for this, it will fail locally often enough, and a crate a little bigger
# than allows is no problem either if it comes to that.
continue-on-error: true
# This job is not required for PR auto-merge, so that sudden announcement of a
# new advisory does not keep otherwise OK pull requests from being integrated.
cargo-deny-advisories:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
with:
command: check advisories
arguments: --workspace --all-features
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
with:
command: check bans licenses sources
arguments: --workspace --all-features
wasm:
name: WebAssembly
runs-on: ubuntu-latest
strategy:
matrix:
target: [ wasm32-unknown-unknown, wasm32-wasip1 ]
env:
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
run: |
rustup update stable
rustup default stable
rustup target add "$TARGET"
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: 'WASI only: crates without feature toggle'
if: endsWith(matrix.target, '-wasi')
run: |
set -x
for crate in src-sec; do
cargo build -p "$crate" --target "$TARGET"
done
- name: crates without feature toggles
run: |
crates=(
src-actor
src-attributes
src-bitmap
src-chunk
src-command
src-config-value
src-date
src-glob
src-mailmap
src-packetline
src-path
src-pathspec
src-prompt
src-quote
src-url
src-validate
)
set -x
for crate in "${crates[@]}"; do
cargo build -p "$crate" --target "$TARGET"
done
- name: features of src-features
run: |
set -x
for feature in progress parallel io-pipe crc32 zlib cache-efficiency-debug; do
cargo build -p src-features --features "$feature" --target "$TARGET"
done
- name: crates with 'sha1' and 'wasm' feature
run: |
set -x
for crate in src-pack; do
cargo build -p "$crate" --features sha1,wasm --target "$TARGET"
done
- name: crates with 'sha1' feature
run: |
crates=(
src-commitgraph
src-hash
src-hashtable
src-object
src-refspec
src-revision
src-traverse
)
set -x
for crate in "${crates[@]}"; do
cargo build -p "$crate" --features sha1 --target "$TARGET"
done
- name: src-pack with all features (including wasm)
run: cargo build -p src-pack --all-features --target "$TARGET"
# Check that all `actions/checkout` in CI jobs have `persist-credentials: false`.
check-no-persist-credentials:
runs-on: ubuntu-slim
env:
GLOB: .github/workflows/*.@(yaml|yml)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
sparse-checkout: '.github/workflows'
- name: List workflows to be scanned
run: |
shopt -s extglob
printf '%s\n' $GLOB # Pathname expansion in $GLOB intended.
- name: Scan workflows
run: |
shopt -s extglob
yq '.jobs.*.steps[]
| select(.uses == "actions/checkout@*" and .with.["persist-credentials"]? != false)
| {"file": filename, "line": line, "name": (.name // .uses)}
| .file + ":" + (.line | tostring) + ": " + .name
' -- $GLOB >query-output.txt # Pathname expansion in $GLOB intended.
cat query-output.txt
test -z "$(<query-output.txt)" # Report failure if we found anything.
# Check that only jobs intended not to block PR auto-merge are omitted as
# dependencies of the `tests-pass` job below, so that whenever a job is
# added, a decision is made about whether it must pass for PRs to merge.
check-blocking:
runs-on: ubuntu-slim
env:
# List all jobs that are intended NOT to block PR auto-merge here.
EXPECTED_NONBLOCKING_JOBS: |-
cargo-deny-advisories
wasm
tests-pass
defaults:
run:
shell: bash # Without this, the shell here is `bash` but without `-o pipefail`.
steps:
- name: Find this workflow
run: |
relative_workflow_with_ref="${GITHUB_WORKFLOW_REF#"$GITHUB_REPOSITORY/"}"
echo "WORKFLOW_PATH=${relative_workflow_with_ref%@*}" >> "$GITHUB_ENV"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
sparse-checkout: ${{ env.WORKFLOW_PATH }}
- name: Get all jobs
run: yq '.jobs | keys.[]' -- "$WORKFLOW_PATH" | sort | tee all-jobs.txt
- name: Get blocking jobs
run: yq '.jobs.tests-pass.needs.[]' -- "$WORKFLOW_PATH" | sort | tee blocking-jobs.txt
- name: Get jobs we intend do not block
run: sort <<<"$EXPECTED_NONBLOCKING_JOBS" | tee expected-nonblocking-jobs.txt
- name: Each job must block PRs or be declared not to
run: |
sort -m blocking-jobs.txt expected-nonblocking-jobs.txt |
diff --color=always -U1000 - all-jobs.txt
# Dummy job to have a stable name for the "all tests pass" requirement.
tests-pass:
name: Tests pass
permissions: {}
needs:
- msrv
- msrv-badge
- pure-rust-build
- test
- test-journey
- test-fast
- test-fixtures-windows
- test-32bit
- test-32bit-windows-size-doc
- lint
- cargo-deny
- check-no-persist-credentials
- check-blocking
if: always() # Always run even if dependencies fail.
runs-on: ubuntu-slim
steps:
- name: Fail if ANY dependency has failed or cancelled
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
- name: OK
run: exit 0

45
.github/workflows/cifuzz.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: CIFuzz
on:
pull_request:
branches:
- main
paths:
- '.github/**'
- 'ci/**'
- 'etc/**'
- 'src/**'
- 'tests/**'
- 'cargo-*/**'
- 'src*/**'
- '*.toml'
- Makefile
workflow_dispatch:
permissions: {} # The fuzzing actions don't use our github.token at all.
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master # zizmor: ignore[unpinned-uses]
with:
oss-fuzz-project-name: gitoxide
language: rust
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master # zizmor: ignore[unpinned-uses]
with:
oss-fuzz-project-name: gitoxide
language: rust
fuzz-seconds: 600
- name: Upload Crash
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

111
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,111 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: CodeQL
on:
push:
branches:
- main
- 'run-ci/**'
- '**/run-ci/**'
pull_request:
branches:
- main
schedule:
- cron: '32 3 * * 6'
workflow_dispatch:
permissions: {} # Expanded in the `analyze` job.
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write # Required for uploading SARIF to view in the Security tab.
# Required to fetch internal or private CodeQL packs, but we don't use any such packs.
# packages: read
# Not needed in public repos. Although supporting private forks can be useful, such as for
# testing vulnerability fixes in a private reupload, other steps would have to be taken to
# run CodeQL in a private repository, so enabling these wouldn't be enough "out of the box."
# actions: read
# contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: rust
build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
category: "/language:${{matrix.language}}"

23
.github/workflows/cron.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: cron
on:
schedule:
- cron: '0 13,1 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
stress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: stress
run: make stress

566
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,566 @@
# Much of this workflow is adapted from the ripgrep release workflow.
# https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml
name: release
on:
push:
# Enable when testing release infrastructure on a branch.
# branches:
# - fix-releases
tags:
# For now, real releases always use `workflow_dispatch`, and running the workflow on tag pushes
# is only done in testing. This is because we usually push too many tags at once for the `push`
# event to be triggered, since there are usually more than 3 crates tagged together. So the
# `push` trigger doesn't usually work. If we allow it, we risk running the workflow twice if
# it is also manually triggered based on the assumption that it would not run. See #1970 for
# details. See also the `run-release-workflow` and `roll-release` recipes in the `justfile`.
# - 'v*'
- 'v*-DO-NOT-USE' # Pattern for tags used to test the workflow (usually done in a fork).
workflow_dispatch:
permissions:
contents: read # This is set more permissively in jobs that need `write`.
defaults:
run:
shell: bash # Use `bash` even in the Windows jobs.
jobs:
# Create a draft release, initially with no binary assets attached.
create-release:
runs-on: ubuntu-slim
permissions:
contents: write # Allows the use of `gh release create`.
# env:
# # Set to force version number, e.g., when no tag exists.
# VERSION: TEST-0.0.0
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Get the release version from the tag
if: env.VERSION == ''
run: echo "VERSION=$REF_NAME" >> "$GITHUB_ENV"
env:
REF_NAME: ${{ github.ref_name }}
- name: Validate version against Cargo.toml
run: |
manifest_version="$(yq -r .package.version Cargo.toml)"
echo "version to name the release: $VERSION"
echo "version Cargo.toml suggests: v$manifest_version"
case "$VERSION" in
"v$manifest_version" )
echo 'OK: Release name/version agrees with Cargo.toml version.'
;;
TEST-* | *-DO-NOT-USE ) # NOTE: If changed, change it in `announce-release` below, too.
echo 'OK: Release name/version is strange but marked as such.'
;;
"$manifest_version" )
echo 'STOPPING: Release name/version is missing the leading "v".'
exit 1
;;
* )
echo 'STOPPING: Release name/version and Cargo.toml version do not match.'
echo 'STOPPING: Usually this means either a wrong tag name or wrong version in Cargo.toml.'
echo 'STOPPING: If intended, prepend `TEST-` or append `-DO-NOT-USE` to the release name.'
exit 1
;;
esac
- name: Create GitHub release
run: gh release create "$VERSION" --title="$VERSION" --draft
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
outputs:
version: ${{ env.VERSION }}
# Build for a particular feature and target, and attach an archive for it.
build-release:
needs: [ create-release ]
strategy:
matrix:
target:
- x86_64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- i686-unknown-linux-musl
- i686-unknown-linux-gnu
- aarch64-unknown-linux-musl
- aarch64-unknown-linux-gnu
- arm-unknown-linux-musleabihf
- arm-unknown-linux-gnueabihf
- powerpc64le-unknown-linux-gnu
- riscv64gc-unknown-linux-gnu
- s390x-unknown-linux-gnu
- x86_64-apple-darwin
- aarch64-apple-darwin
- x86_64-pc-windows-msvc
- x86_64-pc-windows-gnu
- i686-pc-windows-msvc
- aarch64-pc-windows-msvc
# When changing these features, make the same change in build-macos-universal2-release.
feature: [ small, lean, max, max-pure ]
include:
- rust: stable
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: i686-unknown-linux-musl
os: ubuntu-latest
- target: i686-unknown-linux-gnu
os: ubuntu-latest
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
- target: arm-unknown-linux-musleabihf
os: ubuntu-latest
- target: arm-unknown-linux-gnueabihf
os: ubuntu-latest
- target: powerpc64le-unknown-linux-gnu
os: ubuntu-latest
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-latest
- target: s390x-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-apple-darwin
os: macos-15-intel
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: x86_64-pc-windows-gnu
os: windows-latest
rust: stable-x86_64-gnu
- target: i686-pc-windows-msvc
os: windows-latest
- target: aarch64-pc-windows-msvc
os: windows-latest
# On linux we build with musl which causes trouble with open-ssl. For now, just build max-pure there.
# It's a TODO. See https://github.com/GitoxideLabs/gitoxide/issues/1242.
exclude:
- target: x86_64-unknown-linux-musl
feature: small
- target: x86_64-unknown-linux-musl
feature: lean
- target: x86_64-unknown-linux-musl
feature: max
- target: x86_64-unknown-linux-gnu
feature: small
- target: x86_64-unknown-linux-gnu
feature: lean
- target: x86_64-unknown-linux-gnu
feature: max
- target: i686-unknown-linux-musl
feature: small
- target: i686-unknown-linux-musl
feature: lean
- target: i686-unknown-linux-musl
feature: max
- target: i686-unknown-linux-gnu
feature: small
- target: i686-unknown-linux-gnu
feature: lean
- target: i686-unknown-linux-gnu
feature: max
- target: aarch64-unknown-linux-musl
feature: small
- target: aarch64-unknown-linux-musl
feature: lean
- target: aarch64-unknown-linux-musl
feature: max
- target: aarch64-unknown-linux-gnu
feature: small
- target: aarch64-unknown-linux-gnu
feature: lean
- target: aarch64-unknown-linux-gnu
feature: max
- target: arm-unknown-linux-musleabihf
feature: small
- target: arm-unknown-linux-musleabihf
feature: lean
- target: arm-unknown-linux-musleabihf
feature: max
- target: arm-unknown-linux-gnueabihf
feature: small
- target: arm-unknown-linux-gnueabihf
feature: lean
- target: arm-unknown-linux-gnueabihf
feature: max
- target: powerpc64le-unknown-linux-gnu
feature: small
- target: powerpc64le-unknown-linux-gnu
feature: lean
- target: powerpc64le-unknown-linux-gnu
feature: max
- target: riscv64gc-unknown-linux-gnu
feature: small
- target: riscv64gc-unknown-linux-gnu
feature: lean
- target: riscv64gc-unknown-linux-gnu
feature: max
- target: s390x-unknown-linux-gnu
feature: small
- target: s390x-unknown-linux-gnu
feature: lean
- target: s390x-unknown-linux-gnu
feature: max
runs-on: ${{ matrix.os }}
permissions:
contents: write # Allows the use of `gh release upload`.
env:
RUST_BACKTRACE: '1' # Emit backtraces on panics.
CARGO_TERM_COLOR: always
CLICOLOR: '1'
CARGO: cargo # On Linux, this will be changed to `cross` in a later step.
FEATURE: ${{ matrix.feature }}
VERSION: ${{ needs.create-release.outputs.version }}
TARGET: ${{ matrix.target }}
TARGET_FLAGS: --target=${{ matrix.target }}
TARGET_DIR: target/${{ matrix.target }}
PROFILE: release-github
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install packages (Ubuntu)
# Because openssl doesn't work on musl by default, we resort to max-pure.
# And that won't need any dependency, so we can skip this or use `continue-on-error`.
if: matrix.os == 'ubuntu-latest-disabled'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools
- name: Install Rust
env:
RUST: ${{ matrix.rust }}
run: |
rustup update "$RUST"
rustup default "$RUST"
rustup target add "$TARGET"
- name: Use Cross
if: matrix.os == 'ubuntu-latest'
run: |
cargo install cross
echo 'CARGO=cross' >> "$GITHUB_ENV"
- name: Show command used for Cargo
run: |
echo "cargo command is: $CARGO"
echo "target flag is: $TARGET_FLAGS"
echo "target dir is: $TARGET_DIR"
- name: Build release binary (with extra optimizations)
run: |
"$CARGO" build --verbose --profile="$PROFILE" "$TARGET_FLAGS" --no-default-features --features="$FEATURE"
- name: Determine archive basename
run: echo "ARCHIVE=gitoxide-$FEATURE-$VERSION-$TARGET" >> "$GITHUB_ENV"
- name: Pre-populate directory for archive
run: |
mkdir -- "$ARCHIVE"
cp -- {README.md,LICENSE-*,CHANGELOG.md} "$ARCHIVE/"
- name: Build archive (Windows)
if: matrix.os == 'windows-latest'
run: |
file -- "$TARGET_DIR/$PROFILE"/{ein,src}.exe
cp -- "$TARGET_DIR/$PROFILE"/{ein,src}.exe "$ARCHIVE/"
7z a "$ARCHIVE.zip" "$ARCHIVE"
/usr/bin/core_perl/shasum --algorithm=256 --binary -- "$ARCHIVE.zip" > "$ARCHIVE.zip.sha256"
echo "ASSET=$ARCHIVE.zip" >> "$GITHUB_ENV"
echo "ASSET_SUM=$ARCHIVE.zip.sha256" >> "$GITHUB_ENV"
- name: Build archive (Unix)
if: matrix.os != 'windows-latest'
run: |
file -- "$TARGET_DIR/$PROFILE"/{ein,src}
cp -- "$TARGET_DIR/$PROFILE"/{ein,src} "$ARCHIVE/"
tar czf "$ARCHIVE.tar.gz" -- "$ARCHIVE"
shasum --algorithm=256 --binary -- "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256"
echo "ASSET=$ARCHIVE.tar.gz" >> "$GITHUB_ENV"
echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> "$GITHUB_ENV"
- name: Upload release archive
run: gh release upload "$VERSION" "$ASSET" "$ASSET_SUM"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Add a macOS universal binary archive for a feature using its built aarch64 and x86_64 assets.
build-macos-universal2-release:
runs-on: macos-latest
needs: [ create-release, build-release ]
strategy:
matrix:
# These features need to be exactly the same as the features in build-release.
feature: [ small, lean, max, max-pure ]
permissions:
contents: write # Allows the use of `gh release upload`.
env:
BASH_ENV: ./helpers.sh
REPOSITORY: ${{ github.repository }}
FEATURE: ${{ matrix.feature }}
VERSION: ${{ needs.create-release.outputs.version }}
steps:
- name: Define helper function
run: |
name() { echo "gitoxide-$FEATURE-$VERSION-$1-apple-darwin"; }
declare -f name >> "$BASH_ENV"
- name: Obtain single-architecture releases
run: |
gh release --repo="$REPOSITORY" download "$VERSION" \
--pattern="$(name aarch64).tar.gz" --pattern="$(name aarch64).tar.gz.sha256" \
--pattern="$(name x86_64).tar.gz" --pattern="$(name x86_64).tar.gz.sha256"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Unpack single-architecture releases
run: |
shasum --check -- "$(name aarch64).tar.gz.sha256" "$(name x86_64).tar.gz.sha256"
tar xf "$(name aarch64).tar.gz"
tar xf "$(name x86_64).tar.gz"
- name: Determine archive basename
run: echo "ARCHIVE=$(name universal)" >> "$GITHUB_ENV"
- name: Pre-populate directory for archive
run: |
cp -R -- "$(name aarch64)" "$ARCHIVE"
rm -- "$ARCHIVE"/{ein,src}
- name: Create Universal 2 binaries
run: |
for bin in ein src; do
lipo -create "$(name aarch64)/$bin" "$(name x86_64)/$bin" -output "$ARCHIVE/$bin"
file -- "$ARCHIVE/$bin"
done
- name: Build archive
run: |
tar czf "$ARCHIVE.tar.gz" -- "$ARCHIVE"
shasum --algorithm=256 --binary -- "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256"
echo "ASSET=$ARCHIVE.tar.gz" >> "$GITHUB_ENV"
echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> "$GITHUB_ENV"
- name: Upload release archive
run: gh release --repo="$REPOSITORY" upload "$VERSION" "$ASSET" "$ASSET_SUM"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Check for some problems, consolidate checksum files into one, and mark the release non-draft.
publish-release:
runs-on: ubuntu-slim
needs: [ create-release, build-release, build-macos-universal2-release ]
permissions:
contents: write # Allows use of `gh release` for `upload`, `delete-asset`, and `edit`.
env:
REPOSITORY: ${{ github.repository }}
VERSION: ${{ needs.create-release.outputs.version }}
steps:
- name: Discover assets
run: |
gh release --repo="$REPOSITORY" view "$VERSION" --json assets --jq '.assets.[].name' > assets.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Show all individual asset names
run: cat assets.txt
# The `features` array is repeated because GHA doesn't support YAML anchors.
# We will check that the macOS `universal` features match the others exactly.
# In the future this and the next step may be removed, or expanded to do more validation.
- name: Extract macOS asset names by architecture
run: |
for arch in aarch64 x86_64 universal; do
grep -Fwe "$arch-apple-darwin" assets.txt | sort | tee -- "$arch.txt"
done
- name: Check macOS archive features
run: |
mask() { sed -E 's/\w+-apple-darwin/<arch>-apple-darwin/' -- "$1.txt"; }
diff -- <(mask aarch64) <(mask universal)
diff -- <(mask x86_64) <(mask universal)
- name: Clean up local temporary macOS asset list files
run: rm {assets,aarch64,x86_64,universal}.txt
- name: Retrieve all individual checksums
run: gh release --repo="$REPOSITORY" download "$VERSION" --pattern='gitoxide-*.sha256'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Concatenate checksums into one file
run: cat gitoxide-*.sha256 > hashes.sha256
- name: Upload the combined checksum file
run: gh release --repo="$REPOSITORY" upload "$VERSION" hashes.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# If any step of any job fails before this, the draft still has the individual checksum files.
- name: Remove the individual checksum file assets
run: |
for sumfile in gitoxide-*.sha256; do
gh release --repo="$REPOSITORY" delete-asset "$VERSION" "$sumfile" --yes
done
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish the release
if: vars.DRY_RUN_RELEASE != 'true' && vars.DRY_RUN_RELEASE != 'yes' && vars.DRY_RUN_RELEASE != '1'
run: gh release --repo="$REPOSITORY" edit "$VERSION" --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Comment in a locked discussion that notifies about only `gitoxide` (e.g. not `src-*`) releases.
announce-release:
runs-on: ubuntu-slim
needs: [ create-release, publish-release ]
permissions:
contents: write # Needed to distinguish unpublished (still draft) from missing releases.
discussions: write # For adding a comment in the announcement discussion.
env:
REPOSITORY: ${{ github.repository }}
VERSION: ${{ needs.create-release.outputs.version }}
DISCUSSION_URL: ${{ vars.RELEASE_ANNOUNCEMENTS_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Find the discussion ID
run: |
[[ "$DISCUSSION_URL" =~ ^https://github\.com/([^/:@]+)/([^/:@]+)/discussions/([0-9]+)$ ]]
owner="${BASH_REMATCH[1]}"
name="${BASH_REMATCH[2]}"
number="${BASH_REMATCH[3]}"
id="$(gh api graphql -f query='
query GetDiscussionId($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
discussion(number: $number) {
id
}
}
}' -F owner="$owner" -F name="$name" -F number="$number" --jq .data.repository.discussion.id)"
echo "DISCUSSION_ID=$id" >> "$GITHUB_ENV"
- name: Avoid announcing a test in a non-test thread
run: |
case "$VERSION" in
TEST-* | *-DO-NOT-USE ) # NOTE: Should be the same pattern as in `create-release` above.
echo "The release name indicates testing, so we'll only post if the thread is for that."
;;
* )
is_draft="$(gh release --repo="$REPOSITORY" view "$VERSION" --json isDraft --jq .isDraft)"
if [ "$is_draft" = false ]; then
exit 0 # OK to post in a non-test announcement thread.
fi
echo "The release is not published, so we'll only post if the thread is for testing."
;;
esac
title="$(gh api graphql -f query='
query($id: ID!) {
node(id: $id) {
... on Discussion {
title
}
}
}' -F id="$DISCUSSION_ID" --jq .data.node.title)"
grep -Eiqz '^[[(]?test\b' <<<"$title"
- name: Post the comment
run: |
grep -Eqx '[[:alnum:]._+-]+' <<<"$VERSION" # Ensure the version needs no sanitization.
release_url="https://github.com/$REPOSITORY/releases/tag/$VERSION"
comment_body="\`gitoxide\` [$VERSION]($release_url) has been released."
gh api graphql -f query='
mutation PostComment($discussionId: ID!, $body: String!) {
addDiscussionComment(input: {discussionId: $discussionId, body: $body}) {
comment {
id
body
}
}
}' -F discussionId="$DISCUSSION_ID" -F body="$comment_body"
installation:
strategy:
matrix:
build: [ win-msvc, win-gnu, win32-msvc, win32-gnu ]
include:
- build: win-msvc
os: windows-latest
rust: stable
target: x86_64-pc-windows-msvc
- build: win-gnu
os: windows-latest
rust: stable-x86_64-gnu
target: x86_64-pc-windows-gnu
- build: win32-msvc
os: windows-latest
rust: stable
target: i686-pc-windows-msvc
- build: win32-gnu
os: windows-latest
rust: stable
target: i686-pc-windows-gnu
runs-on: ${{ matrix.os }}
env:
RUST: ${{ matrix.rust }}
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust
run: |
rustup update "$RUST"
rustup default "$RUST"
rustup target add "$TARGET"
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0
with:
msystem: MINGW${{ startsWith(matrix.target, 'i686-') && '32' || '64' }}
pacboy: cc:p
path-type: inherit
- name: 'Installation from crates.io: gitoxide'
run: |
cargo +"$RUST" install --target "$TARGET" --no-default-features \
--features max-pure --target-dir install-artifacts --debug --force gitoxide
shell: msys2 {0}

31
.github/workflows/zizmor.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: GitHub Actions Security Analysis with zizmor 🌈
on:
push:
branches:
- main
- 'run-ci/**'
- '**/run-ci/**'
pull_request:
branches:
- main
workflow_dispatch:
permissions: {} # Expanded in the `zizmor` job.
jobs:
zizmor:
runs-on: ubuntu-latest
permissions:
security-events: write # Required for uploading SARIF to view in the Security tab.
contents: read # Not needed in public repos. (Kept for private forks/reuploads.)
actions: read # Not needed in public repos. (Kept for private forks/reuploads.)
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0
with:
persona: pedantic

5
.github/zizmor.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
rules:
anonymous-definition:
disable: true
concurrency-limits:
disable: true