# Pre-commit hook

A pre-commit hook is the cheapest place to enforce commit policy: the developer
finds out while they are still writing the message, not after a CI round trip.

Add Commit Check to `.pre-commit-config.yaml`:

```yaml title=".pre-commit-config.yaml"
repos:
  - repo: https://github.com/commit-check/commit-check
    rev: v2.19.5
    hooks:
      - id: check-message
      - id: check-branch
      - id: check-author-name
      - id: check-author-email
```

Then install the hooks. `check-message` runs at the `commit-msg` stage, so it
needs its own install step:

```console
$ pre-commit install --hook-type commit-msg
$ pre-commit install
```

That is it. The next malformed commit message is rejected before it exists.

!!! warning "If `check-message` never runs"

    It is almost always because `pre-commit install --hook-type commit-msg` was
    not run — a plain `pre-commit install` only wires up the `pre-commit` stage.
    More in [Troubleshooting](../troubleshoot.md).

## Available hooks

| Hook ID | Stage | Rules |
|---|---|---|
| `check-message` | `commit-msg` | [CC001–CC016](../rules.md#commit-message-rules) |
| `check-branch` | `pre-commit` | [CC201–CC202](../rules.md#branch-rules) |
| `check-author-name` | `pre-commit` | [CC101](../rules.md#cc101) |
| `check-author-email` | `pre-commit` | [CC102](../rules.md#cc102) |
| `check-no-force-push` | `pre-push` | [CC301](../rules.md#cc301) |
| `check-files` | `pre-push` | [CC302–CC304](../rules.md#push-rules) |
| `check-tag` | `pre-push` | [CC401](../rules.md#cc401) |

The three `pre-push` hooks need their own install too:

```console
$ pre-commit install --hook-type pre-push
```

`check-tag` checks the tags the push sends: the tag a tag push names, and on
a branch push, such as `git push --follow-tags`, the tags on every commit the
push carries.

pre-commit works out which commits a push carries from your remote-tracking
refs, such as `origin/main`, and runs no hook for a push that creates a ref
without carrying new commits by that measure. So a new tag pushed on its own,
when the commit it points at is already in a remote-tracking ref, is never seen
by `check-tag`. The same holds for a tag that `git push --follow-tags` sends
along with a branch push in that case.

## Configuring without a TOML file

Options can be passed as hook arguments, which keeps everything in one file:

```yaml title=".pre-commit-config.yaml"
repos:
  - repo: https://github.com/commit-check/commit-check
    rev: v2.19.5
    hooks:
      - id: check-message
        args:
          - --subject-imperative=true
          - --subject-max-length=72
          - --allow-merge-commits=false
```

A `cchk.toml` is usually the better choice once you have more than a couple of
options, because CI and the CLI read it too. See
[Configuration](../configuration.md) for the precedence rules.

## Fixing the message instead of rejecting it

Give `check-message` the `--fix` argument and it corrects a message whose
every failure has a mechanical fix, such as a type written `Fix` or a missing
colon, and lets the commit through:

```yaml title=".pre-commit-config.yaml"
repos:
  - repo: https://github.com/commit-check/commit-check
    rev: v2.19.5
    hooks:
      - id: check-message
        args: [--fix]
```

With that, `git commit -m "Fix: add streaming support"` records
`fix: add streaming support`. A message that needs a judgment, such as one
with no type at all, is rejected as before. More in
[Fixing a message in place](../example.md#fixing-a-message-in-place).

## Skipping a hook

Occasionally you need to get a commit through — a mid-rebase fixup, an
automated migration. `pre-commit` supports this natively:

```console
$ SKIP=check-message git commit -m "wip"
```

!!! warning "Local hooks are not a policy boundary"

    Anyone can pass `--no-verify`. Hooks exist to give fast feedback to people
    who want to follow the policy, not to stop people who don't. Pair them with
    the [GitHub Action](github-actions.md), which runs where it cannot be
    skipped.
