Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

114 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

git-smee

CI

Smee - the right hand of (Git) hooks

git-smee is a lightweight Rust CLI that manages Git hooks from a version-controlled configuration file, .git-smee.toml, in your repository.

Instead of copying hook scripts around or relying on heavy external tooling, git-smee installs small, idempotent hook wrappers that delegate to commands defined in your config—making hook behavior consistent across all contributors.

Why git-smee?

  • Version-controlled: Define hooks once in .git-smee.toml, commit it, share it.
  • Cross-platform minded: Designed to work on Unix and Windows (Git Bash) via a clear platform abstraction.
  • Portable Unix execution: Hook commands run through POSIX sh -c on Unix-like systems (not Bash-specific).
  • Small and focused: Single-purpose binary, no plugin ecosystem or extra runtime.

Installation

Homebrew

brew tap errfld/git-smee
brew install git-smee

The Homebrew tap installs prebuilt release artifacts for Apple Silicon macOS (aarch64-apple-darwin), Intel macOS (x86_64-apple-darwin), and Intel Linuxbrew (x86_64-unknown-linux-gnu).

Cargo

cargo install git-smee-cli

The minimum supported Rust version is 1.88.0.

From source

git clone https://github.com/errfld/git-smee.git
cd git-smee
cargo install --path crates/git-smee-cli

Quick Start

  1. Initialize a configuration file in your repository:

    git smee init

    This creates a .git-smee.toml file with a default pre-commit hook. If the file already exists, init refuses to overwrite it unless you pass --force.

    You can start from an explicit built-in template and then edit the generated TOML:

    git smee init --template rust
    git smee init --template node-pnpm
    git smee init --template generic

    Available templates are:

    • minimal (default): a single starter pre-commit command.
    • rust: cargo fmt --all -- --check, cargo clippy --workspace --all-targets --all-features -- -D warnings, and cargo test --workspace --all-targets --all-features.
    • node-pnpm: pnpm lint and pnpm test hooks.
    • generic: an editable shell-command starter with commented examples.

    Unknown template names are rejected with an error that lists valid template names.

  2. Edit .git-smee.toml to define your hooks:

    [[pre-commit]]
    command = "cargo fmt --check"
    
    [[pre-commit]]
    command = "cargo test"
    
    [[pre-push]]
    command = "cargo test --all-targets"
  3. Install the hooks into .git/hooks:

    git smee install

    By default, install only overwrites hook files previously managed by git-smee. Existing unmanaged hook files are preserved unless you pass --force. When a hook phase is removed from .git-smee.toml, install also prunes the now-obsolete managed wrapper for that phase so stale hooks do not keep running; unmanaged files for removed phases are left untouched. If the repository uses core.hooksPath and that directory does not exist yet, install creates the effective hooks directory before writing managed wrappers.

That's it! Your hooks are now active. When Git triggers a hook, the installed wrapper runs the git-smee executable directly and executes the configured commands in order.

Alternate config paths

By default, git-smee reads .git-smee.toml from the repository root.

You can override the config path in two ways:

  • CLI flag: --config <path>
  • Environment variable: GIT_SMEE_CONFIG=<path>

Precedence is explicit: --config > GIT_SMEE_CONFIG > .git-smee.toml.

Examples:

git smee install --config .config/git-smee.toml
GIT_SMEE_CONFIG=.config/git-smee.toml git smee run pre-commit

Configuration

The .git-smee.toml file uses TOML format. Each hook is defined as an array of tables:

[[hook-name]]
command = "your command here"

Hook Definition Fields

Field Type Required Description
command string yes The command to execute
parallel_execution_allowed bool no Allow parallel execution with other parallel-enabled commands (default: false)

Execution Order

When running hooks, git-smee executes commands in two phases:

  1. Sequential phase: All commands with parallel_execution_allowed = false (or omitted) run one at a time, in the order they appear in the config.
  2. Parallel phase: All commands with parallel_execution_allowed = true run concurrently using a thread pool.

Sequential commands always complete before parallel commands begin. If any sequential command fails, execution stops immediately. For parallel commands, the first failing command causes the overall run to fail, but commands that are already in flight may still finish; git-smee reports failures deterministically by configured parallel command order for the commands that were attempted.

After each git smee run <hook>, git-smee prints a compact summary after the hook command output:

Hook summary: pre-commit
  total: 2 attempted, 0 skipped, 0 failed in 128ms
  sequential: 1 attempted, 0 failed in 48ms
  parallel: 1 attempted, 0 failed in 80ms
  - sequential command #1: ok in 48ms
  - parallel command #1: ok in 80ms

Failed runs include the first failing command and exit code while preserving the command's own output:

Hook summary: pre-commit
  total: 1 attempted, 1 skipped, 1 failed in 12ms
  sequential: 1 attempted, 1 failed in 12ms
  parallel: 0 attempted, 0 failed in 0ms
  - sequential command #1: failed with code 1 in 12ms
  first failure: sequential command #1 exited with code 1

Example:

[[pre-commit]]
command = "cargo fmt --check"
# Sequential (default) - runs first

[[pre-commit]]
command = "cargo clippy"
parallel_execution_allowed = true
# Parallel - runs concurrently with other parallel commands

[[pre-commit]]
command = "cargo test --lib"
parallel_execution_allowed = true
# Parallel - runs concurrently with clippy

[[pre-commit]]
command = "echo 'Setup complete'"
# Sequential - runs before parallel commands

In this example, the two sequential commands (cargo fmt --check and echo 'Setup complete') run first in order, then cargo clippy and cargo test --lib run in parallel.

Supported Git Hooks

git-smee supports all standard Git lifecycle hooks:

Hook Description
applypatch-msg Edit the commit message of a patch
pre-applypatch Run before a patch is applied
post-applypatch Run after a patch is applied
pre-commit Run before a commit is created
prepare-commit-msg Prepare the default commit message
commit-msg Validate or modify the commit message
post-commit Run after a commit is created
pre-merge-commit Run before a merge commit is created
pre-rebase Run before a rebase starts
post-checkout Run after a checkout
post-merge Run after a merge
post-rewrite Run after commands that rewrite commits
pre-push Run before a push
pre-receive Run before refs are updated (server-side)
update Run once per ref update (server-side)
proc-receive Handle receive-pack commands (server-side)
post-receive Run after refs are updated (server-side)
reference-transaction Run when reference transaction state changes
push-to-checkout Run when a push tries to update the checked-out branch
pre-auto-gc Run before automatic garbage collection
post-update Run after refs are updated (server-side)
fsmonitor-watchman Integration with watchman file monitor
post-index-change Run after the index is written

Hook argument forwarding

When Git invokes a hook with positional arguments (for example commit-msg <path> or post-checkout <old> <new> <flag>), installed git-smee wrappers forward those arguments to git smee run.

Forwarded hook arguments are available as shell positional parameters inside configured commands: $1, $2, ... on Unix and %1, %2, ... on Windows. Examples:

[[commit-msg]]
command = "test -n \"$1\""
[[commit-msg]]
command = "if \"%1\"==\"COMMIT_EDITMSG\" exit /b 0"

On all platforms, git-smee also exports forwarded hook args as environment variables:

  • GIT_SMEE_HOOK_ARGC: number of forwarded args
  • GIT_SMEE_HOOK_ARG_1 ... GIT_SMEE_HOOK_ARG_N: individual argument values

When git smee run receives stdin, it buffers that payload once and replays identical bytes to each configured command for the hook. This keeps stdin-driven hooks such as pre-push, pre-receive, and post-receive deterministic even when multiple commands are configured for the same phase. The default buffered-stdin limit is 10 MiB; set GIT_SMEE_HOOK_STDIN_LIMIT_BYTES=<bytes> to raise or lower it for unusually large hooks.

proc-receive is an interactive pkt-line protocol, so git-smee does not pre-buffer stdin for that phase. The configured proc-receive command inherits stdin directly to avoid blocking the protocol handshake before the command starts.

CLI Commands

git smee init [--force] [--config <path>]       # Initialize a config file
git smee install [--force] [--config <path>]    # Install hooks from the selected config
git smee [--config <path>] run <hook> [hook-args...]           # Run a specific git hook
git smee [--config <path>] status [--json]      # Show hook coverage and drift
git smee [--config <path>] doctor [--json]      # Diagnose repository setup and hook drift
git smee migrate-hooks                          # Suggest config entries for existing hooks

Run git smee status as a read-only onboarding or CI smoke check after editing .git-smee.toml or reinstalling wrappers. It summarizes each configured phase, command counts, missing/unmanaged/stale wrappers, obsolete managed wrappers, and next actions. For example:

git-smee status: Drift
configured hooks:
  - pre-commit: configured commands=1, installed (.git/hooks/pre-commit)
  - pre-push: configured commands=1, missing (.git/hooks/pre-push)
    next: run git smee install to create pre-push
obsolete managed hooks:
  - commit-msg: obsolete managed wrapper (.git/hooks/commit-msg)
    next: remove obsolete managed hook .git/hooks/commit-msg
next actions:
  - remove obsolete managed hook .git/hooks/commit-msg
  - run git smee install to create pre-push

Use git smee status --json when tooling needs stable fields such as status, hooks[].configured_command_count, hooks[].state, obsolete_managed_hooks, and next_actions.

Run git smee migrate-hooks before replacing an existing .git/hooks/* setup. It is read-only: unmanaged Git hook files are reported as parseable TOML snippets that call a preserved legacy copy outside the managed hooks directory (for example .git-smee/legacy/pre-commit), while git-smee-managed wrappers are ignored and listed in a comment. Move each legacy script as instructed before copying the suggestions into .git-smee.toml and running git smee install.

Run git smee doctor when onboarding a repository, after changing core.hooksPath, or when a hook does not fire as expected. The human-readable report groups ok, warnings, and errors with remediation commands; --json emits the same stable fields for automation. Doctor exits successfully when no errors are present and exits non-zero when setup errors need action.

How it works (high level)

  1. You declare hooks in .git-smee.toml:

    [[pre-commit]]
    command = "cargo fmt --check"
    
    [[pre-commit]]
    command = "cargo test"
    
    [[pre-push]]
    command = "cargo test --all-targets"
  2. git-smee reads this config and knows:

    • which Git hook name (e.g. pre-commit) maps to which commands,
    • that each [[hook-name]] entry is an ordered HookDefinition.
  3. The installer will write idempotent scripts into Git's effective hooks directory:

    • Each script runs the installed git-smee executable directly with --config <resolved path> run <hook>, forwarding original Git hook positional arguments.
  4. The executor will run the configured commands for that hook and propagate exit codes back to Git.

Project structure

This repo is a Cargo workspace with two crates:

  • crates/git-smee-core
    • Library crate with all domain logic:
      • .git-smee.toml parsing (SmeeConfig, HookDefinition)
      • Error types using thiserror
      • Installer, executor, platform abstraction
  • crates/git-smee-cli
    • Binary crate providing the git smee CLI:
      • Uses clap for argument parsing
      • Thin wrapper that delegates to git-smee-core

Status

Early-stage / experimental. Expect breaking changes while the design settles.

Development

From the repo root:

cargo build
cargo test
cargo run -p git-smee-cli -- --help

For local CI parity checks:

cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
rustup run stable cargo llvm-cov --workspace --all-features --locked --summary-only -- --skip repository::tests::
cargo audit

GitHub Actions CI validates Linux (stable/beta/nightly), macOS (stable), Windows (stable), and MSRV (1.88.0) build/test compatibility.

Tests that mutate process-global state (environment variables or the current working directory) must hold the shared process_state_lock test helper for the entire setup/exercise/restore window. Do not add module-local env/cwd mutexes: Rust 2024 treats environment mutation as unsafe unless all process-wide access is externally synchronized.

Release archives publish adjacent .sha256 files. Verify downloads with:

shasum -a 256 -c git-smee-<version>-<target>.tar.gz.sha256

License

MIT © 2025 Eran Riesenfeld

About

Git Smee - Hook(s) helper

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages