Lefthook

repository·master·Indexed 9 days ago

https://github.com/evilmartians/lefthook

A fast, powerful, and simple Git hooks manager written in Go. It allows developers to automate tasks like linting, testing, and security audits during Git operations using a single, dependency-free binary. Supports configuration in YAML, TOML, JSON, and JSONC, and provides a CLI for installing, running, and validating hooks.

Tokens
37.5K
Snippets
197
Records
229
Agent score
91%

What's inside Lefthook

  1. Understand `glob` behavior with `**` and `root`

    master

    When configuring globs in Lefthook, be aware of these specific behaviors:

    • The ** pattern: Unlike many other tools, the ** pattern in Lefthook matches 1 or more directories deep, not zero or more.
      • glob: "src/**/*.js" will not match src/file.js.
      • To match files at both the top level and nested, you must use separate patterns or configure glob_matcher: doublestar.
    • The root option: Globs are always calculated relative to the actual root of the git repository; the root configuration option is ignored during glob calculation.
    # Does NOT match src/file.js
    glob: "src/**/*.js"
    
    # Matches src/file.js only
    glob: "src/*.js"
  2. Override and extend Lefthook configuration

    master

    When lefthook-local.yml is present, Lefthook merges its content with lefthook.yml.

    • Overriding: You can change properties of existing commands (e.g., changing the run command or setting skip: true).
    • Extending: You can add new configuration properties to existing hooks (e.g., setting parallel: true on a hook) or define entirely new hooks (e.g., adding a post-merge hook).

    Example Scenario

    Main configuration (lefthook.yml):

    pre-commit:
      commands:
        lint:
          run: bundle exec rubocop -- {staged_files}
          glob: "*.rb"
        check-links:
          run: lychee -- {staged_files}

    Local overrides (lefthook-local.yml):

    pre-commit:
      parallel: true
      commands:
        lint:
          run: docker-compose run backend {cmd} # Wraps the original command
        check-links:
          skip: true # Disables this specific command
    
    post-merge:
      files: "git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD"
      commands:
        dependencies:
          glob: "Gemfile*"
          run: docker-compose run backend bundle install

    Resulting merged configuration: Lefthook will execute the following logic:

    pre-commit:
      parallel: true
      commands:
        lint:
          run: docker-compose run backend bundle exec rubocop -- {staged_files}
          glob: "*.rb"
        check-links:
          run: lychee -- {staged_files}
          skip: true
    
    post-merge:
      files: "git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD"
      commands:
        dependencies:
          glob: "Gemfile*"
          run: docker-compose run backend bundle install
    # lefthook-local.yml example
    pre-commit:
      parallel: true
      commands:
        lint:
          run: docker-compose run backend {cmd}
        check-links:
          skip: true
  3. How Git LFS support works in Lefthook

    master

    Lefthook automatically manages Git LFS (Large File Storage) hooks for specific Git lifecycle events. If the git-lfs binary is installed and required by your project, Lefthook will execute the relevant LFS hooks during the following Git operations:

    • post-checkout
    • post-commit
    • post-merge
    • pre-push

    If git-lfs is not installed and not required, Lefthook suppresses errors and will not execute these hooks or issue warnings. To see the raw output from Git LFS during execution, set the LEFTHOOK_VERBOSE environment variable.

  4. Filter files by type using `file_types`

    master

    You can restrict a run command to only execute on files that match specific types using the file_types configuration key. This allows you to apply different tools (like linters or formatters) to different categories of files (e.g., only running a JS linter on .js files).

    Logic Rules

    • Basic Types: The types text, binary, executable, not executable, symlink, and not symlink are applied using AND logic. If you provide multiple of these, a file must satisfy all of them.
    • MIME Types: MIME types (e.g., text/x-python) are applied using OR logic. You can combine multiple MIME types to target a set of different file extensions.
    • Constraint: You cannot combine mutually exclusive types like symlink and not symlink in a single command.
    pre-commit:
      commands:
        lint-code:
          run: yarn lint {staged_files}
          file_types: text
  5. Use the `only` option to restrict hook execution

    master

    The only option allows you to force a command, a script, or an entire hook to execute only when specific conditions are met. It functions as the logical opposite of the skip option.

    Important Precedence Rule: The skip option takes precedence over only. If a command or hook satisfies both an only condition and a skip condition that conflict, the execution will be skipped.

    Common use cases include restricting hooks to specific git branches (using ref) or specific git operations (like rebase).

    # Example: Execute a hook only for `dev/*` branches
    
    ```yml
    # lefthook.yml
    
    pre-commit:
      only:
        - ref: dev/*
      commands:
        lint:
          run: yarn lint
        test:
          run: yarn test
  6. Use lefthook-local.yml without a main config file

    master
    If you want to use Lefthook for your own workflow without imposing it on your teammates or modifying the project's existing configuration, you can use lefthook-local.yml on its own. In this mode, Lefthook will look for and execute the jobs defined in the local file, even if no lefthook.yml is present in the repository.
  7. How Lefthook works

    master

    Lefthook manages Git hooks by installing lightweight scripts into your .git/hooks/ directory. When a Git event occurs (like a commit), the installed script executes lefthook run {hook-name}.

    To use Lefthook, you follow two main steps:

    1. Create a lefthook.yml configuration file to define your hooks and commands.
    2. Run lefthook install to apply those configurations to your Git project.
  8. Configure job groups in Lefthook

    master

    You can use the group keyword to bundle multiple jobs together and control their execution strategy. This is useful for running multiple tasks at once or creating a pipeline of tasks.

    Group Execution Options

    • parallel: If set to true, all jobs within the group execute simultaneously.
    • piped: If set to true, jobs execute sequentially, passing the output of one job as the input to the next.
    • jobs: A list of the individual jobs that belong to the group.

    Configuration Inheritance

    When you define certain properties on a group, they are automatically inherited by all underlying jobs within that group. Supported inheritable properties include:

    • env (environment variables)
    • root (working directory)
    • glob (file patterns to match)
    • exclude (files to ignore)

    Merging Groups

    To ensure a group can be merged with settings defined in a local configuration or via extends, you must provide a name to the job that contains the group.

    # lefthook.yml
    
    pre-commit:
      jobs:
        - group:
            parallel: true
            jobs:
              - run: echo 1
              - run: echo 2
              - run: echo 3
  9. Install and uninstall behavior for AI hooks

    master

    The behavior of lefthook install and lefthook uninstall varies depending on the AI provider:

    • Claude, Codex, and Cursor: These providers are additive. lefthook install replaces only the Lefthook-managed entries with fresh ones from your config, leaving user-authored entries intact. lefthook uninstall strips only the Lefthook-managed entries.
    • Copilot: This provider is destructive. lefthook install rewrites .github/hooks/lefthook.json entirely from scratch. lefthook uninstall removes the .github/hooks/lefthook.json file completely.
  10. Filter hook execution using the `root` option

    master
    In pre-push and pre-commit hooks, as well as for the custom files command, the root option acts as a path filter. Lefthook will only execute the command if the relevant files fall within the specified root directory. If all files are filtered out by the root setting, the command will be skipped entirely.
  11. Use local configuration to override hooks

    master

    You can create a lefthook-local.yml file to override or skip specific hooks without modifying the main lefthook.yml file. This is useful for individual developers who want to skip certain commands or adjust settings for their local environment.

    # lefthook-local.yml
    pre-push:
      exclude_tags:
        - frontend
      jobs:
        - name: audit packages
          skip: true
  12. Understand the Lefthook configuration merge order

    master

    When multiple configuration files define the same settings, Lefthook applies them in a specific order. Settings defined in later stages override settings from earlier stages. The precedence order (from lowest to highest) is:

    1. lefthook.yml (the main configuration file)
    2. extends (configs specified in the extends option)
    3. remotes (configs specified in the remotes option)
    4. lefthook-local.yml (the local configuration file)

    This means lefthook-local.yml has the highest priority and can override any setting defined in the other three sources.