Skip to main content

A picky and eager Git hook runner.

Project description

goose
💻🪿

A picky and eager Git hook runner

  • 🔒 Reproducible builds
  • Dynamic parallelism
  • 💨 Small file-system footprint
  • 🏃 Fast Python installs with uv

Installation

Refer to the documentation for alternative installation instructions.

Via uvx

uvx install git-goose

Github Actions

name: CI
on:
  push:
    branches: ["main"]
  pull_request:
jobs:
  lint:
    name: Run goose checks
    uses: antonagestam/goose/.github/workflows/run.yaml@main

Features

  • Smart parallelism schedules hooks across CPUs while avoiding concurrent writes.
  • Deterministic environments by using ecosystem-specific lock files.
  • Environments are shared across hooks.
  • Self-contained definitions means there's no need to push tool-specific configuration upstream, or to maintain brittle mirroring schemes.

Parallelism

Goose takes care to keep your CPUs as busy as possible, optimizing to have the full suite of hooks finish as soon as possible. It does this by distributing units of work to all available processing cores.

Parameterized hooks, or hooks that take files as command line arguments, are divided to one unit of work per available core. Whenever a core becomes available for more work, a new unit is chosen for execution.

The scheduler takes care to never run more than one mutating hook on the same file. It does this by taking into account hooks marked as read_only and by comparing sets of files a unit of work is assigned to. Two incompatible hooks can be simultaneously working on two separate parts of the code-base.

Deterministic environments

Goose uses lock files to facilitate deterministic results across developer environments and CI. You specify dependencies in goose.yaml, and invoking goose run will produce the appropriate lock files under a .goose/ directory. The .goose/ directory is meant to be checked into git, so that future invocations of goose run can use the lock files it contains to produce identical environments for hooks to run in.

---
title: Lock file workflow
---
flowchart LR
  cfg["Config in goose.yaml"] -- goose upgrade --> lf
  lf["Lock files under .goose/"] -- goose run --> env
  env["Environments"]
  • Invoking goose upgrade creates lock files under the in-tree .goose directory.
  • Invoking goose run creates out-of-tree environments from the lock files. By default they live under ~/.cache/goose.
  • Hooks are executed in the generated environments.

Usage

Create a goose.yaml file in the repository root.

environments:
  - id: python
    ecosystem:
      language: python
      version: "3.14"
    dependencies:
      - ruff

hooks:
  - id: ruff
    environment: python
    command: ruff
    args: [check, --force-exclude, --fix]
    types: [python]

  - id: ruff-format
    environment: python
    command: ruff
    args: [format, --force-exclude]
    types: [python]

Bootstrap environments, generate lock files, and install dependencies.

$ goose upgrade

Run all hooks over all files.

$ goose run --select=all

Commit configuration and lock files.

$ git add goose.yaml .goose
$ git commit -m 'Add goose configuration'

Configure goose to run as git hook. Supported hooks are pre-commit and pre-push.

$ goose git-hook pre-commit
$ goose git-hook pre-push

Upgrading hook versions

As pinning of hook versions is handled with lock files, there's no need to change configuration to upgrade hook dependency versions, instead you just run the upgrade command.

$ goose upgrade
$ git add .goose
$ git commit -m 'Bump goose dependencies'

Example node hook

Goose currently supports Python and Node environments, here's an example using Prettier to format Markdown files.

environments:
  - id: node
    ecosystem:
      language: node
      version: "21.7.1"
    dependencies:
      - prettier

hooks:
  - id: prettier
    environment: node
    command: prettier
    types: [markdown]
    args:
      - --write
      - --ignore-unknown
      - --parser=markdown
      - --print-width=88
      - --prose-wrap=always

Read-only hooks

You will likely want to use a mix of pure linters, as well as formatters and auto-fixers. Tools that don't mutate files can be more heavily parallelized by Goose, because they can inspect overlapping sets of files simultaneously as other tools. To enable this you set read_only: true in hook configuration.

environments:
  - id: python
    ecosystem:
      language: python
      version: "3.14"
    dependencies:
      - pre-commit-hooks

hooks:
  - id: check-case-conflict
    environment: python
    command: check-case-conflict
    read_only: true

  - id: check-merge-conflict
    environment: python
    command: check-merge-conflict
    read_only: true
    types: [text]

  - id: python-debug-statements
    environment: python
    command: debug-statement-hook
    read_only: true
    types: [python]

  - id: detect-private-key
    environment: python
    command: detect-private-key
    read_only: true
    types: [text]

  - id: end-of-file-fixer
    environment: python
    command: end-of-file-fixer
    types: [text]

  - id: trailing-whitespace-fixer
    environment: python
    command: trailing-whitespace-fixer
    types: [text]

Hooks that do not specify read_only: true will never run simultaneously as other tools over the same file.

Non-parameterized hooks

Some tools don't support passing files, or just work better if given the responsibility to parallelize work itself. One such tool is mypy. You can instruct goose to not pass filenames to a hook (and as a consequence, also not spawn multiple parallel jobs for this hook).

environments:
  - id: mypy
    ecosystem:
      language: python
      version: "3.14"
    dependencies:
      - mypy

hooks:
  - id: mypy
    environment: mypy
    command: mypy
    read_only: true
    parameterize: false

Environment variables

Hook invocations are called with the same environment variables as goose is invoked with, other than PATH being overridden to point at the environment of the hook.

Static environment variables can be configured in hook definitions. These will overwrite inherited values, but cannot overwrite PATH.

hooks:
  - id: mypy
    environment: type-check
    command: mypy
    env_vars:
      FORCE_COLOR: "1"
    read_only: true
    parameterize: false

Terse and loose environment configs

Environments can be configured less verbosely. The id field can be omitted and will then default to the name of the ecosystem language.

Version can also be omitted in the configuration and will then cause the highest available version to be pinned when calling goose upgrade. Since the ecosystem version is pinned in the manifest regardless of whether it is specified in the config or not, this omitting the version from the config is not any less safe.

environments:
  - ecosystem: python
    dependencies:
      - ruff

hooks:
  - id: ruff
    environment: python
    command: ruff
    args: [check, --force-exclude, --fix]
    types: [python]

  - id: ruff-format
    environment: python
    command: ruff
    args: [format, --force-exclude]
    types: [python]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

git_goose-0.14.3.tar.gz (89.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

git_goose-0.14.3-py3-none-any.whl (33.2 kB view details)

Uploaded Python 3

File details

Details for the file git_goose-0.14.3.tar.gz.

File metadata

  • Download URL: git_goose-0.14.3.tar.gz
  • Upload date:
  • Size: 89.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for git_goose-0.14.3.tar.gz
Algorithm Hash digest
SHA256 1910fb1d8ce472ccec00ef875afcaf6c06a84c6bf0824714f86f6dc439ac5c44
MD5 9de1f7a8915a1709db55bd0e35026f86
BLAKE2b-256 c125fb7e77581fa45d31534b5b821e87cea7754870f135646b15b5e1eb983732

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_goose-0.14.3.tar.gz:

Publisher: release.yaml on antonagestam/goose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file git_goose-0.14.3-py3-none-any.whl.

File metadata

  • Download URL: git_goose-0.14.3-py3-none-any.whl
  • Upload date:
  • Size: 33.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for git_goose-0.14.3-py3-none-any.whl
Algorithm Hash digest
SHA256 210040516b05dc483bef82cd38cd60d031fca3bf0044a7d06d8db4c82b060f01
MD5 a47e5d9ae7d60e42c81d9140832f6136
BLAKE2b-256 c9684ec6c1971e386efbc911a0fd4a538c8a4f8f7493d2934974142f0703b706

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_goose-0.14.3-py3-none-any.whl:

Publisher: release.yaml on antonagestam/goose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page