linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Chapters
  • How it works
  • Lessons
  • Knowledge base
  • Interview prep
home/git/lessons/git-lab-14-1-pre-commit-framework

lesson ── git-labs ── ~20 мин ── 9 шагов

pre-commit framework: automation before every commit

The goal is to bring up the pre-commit framework on a small repo, see how a hook blocks a "bad" commit, how a hook fixes a file automatically, and how --no-verify skips the check.

The sandbox is air-gapped, so you cannot reach github.com. On a real project, hooks usually come from public repositories (repo: https://github.com/...), but here you use repo: local with shell commands. The idea and the framework interface are the same as in production.

The pre-commit framework is already installed in the image via pip (see sandbox-images/git-base/Dockerfile).

▶ интерактивный sandbox

Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.

запустить sandbox →

stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя

Шаги

  1. 01

    Create a repo with a couple of badly formatted files

    bash
    cd /home/student/work
    mkdir -p hooks-lab && cd hooks-lab
    git init -b main
    # double spaces, no space after ":" = typical fmt problems
    cat > config.yaml <<EOF
    port:    8080
    name:foo
    EOF
    # printf instead of echo = write \n literally, no trailing newline
    printf "name = 'world'\nprint('hi',name )\n" > app.py
    git add . && git commit -m "initial messy code"

    The files are badly formatted on purpose. Pre-commit will catch this in a moment.

    ✓ A repo with messy files is created.

  2. 02

    Check that pre-commit is installed

    bash
    which pre-commit              # path to the binary in $PATH
    pre-commit --version          # framework version

    It should print a path and version 4.0.1. If nothing is found, the image was built without python+pre-commit, so rebuild sandbox-images/git-base/.

    подсказка

    `which` shows the path to the binary. `pre-commit --version` shows its version.

    ✓ pre-commit found.

  3. 03

    Write a .pre-commit-config.yaml with local hooks

    In the air-gapped sandbox you use repo: local. The hook is defined entirely in the config: name, command, what it does. On a real project you would write repo: https://github.com/pre-commit/pre-commit-hooks and a list of id: from there, and the result is the same.

    bash
    cd /home/student/work/hooks-lab
    cat > .pre-commit-config.yaml <<'EOF'
    repos:
      - repo: local
        hooks:
          - id: end-of-file-fixer
            name: ensure file ends with newline
            language: system
            entry: bash -c 'for f in "$@"; do [ -s "$f" ] && [ "$(tail -c1 "$f" | wc -l)" -eq 0 ] && echo "" >> "$f"; done' --
            types: [text]
          - id: trailing-whitespace
            name: strip trailing whitespace
            language: system
            entry: bash -c 'for f in "$@"; do sed -i "s/[[:space:]]*$//" "$f"; done' --
            types: [text]
          - id: forbid-fixme
            name: block FIXME in committed code
            language: system
            entry: bash -c 'for f in "$@"; do grep -nH "FIXME" "$f" && exit 1; done; exit 0' --
            types: [text]
    EOF
    git add .pre-commit-config.yaml
    git commit -m "add pre-commit config"

    Three hooks: the first two fix the file themselves, the third complains about FIXME and does not let the commit through.

    ✓ The config is committed.

  4. 04

    Activate pre-commit for the repo

    pre-commit install creates .git/hooks/pre-commit, which on every commit will run the hooks from the config.

    bash
    cd /home/student/work/hooks-lab
    pre-commit install            # creates .git/hooks/pre-commit -> runs the framework
    ls .git/hooks/pre-commit

    The file should appear.

    ✓ The hook is registered. Next, try a commit.

  5. 05

    Make a commit and see how pre-commit fixes a file

    config.yaml has no final newline. end-of-file-fixer will fix that. First change the file so pre-commit sees it staged:

    bash
    cd /home/student/work/hooks-lab
    printf 'name:foo' > config.yaml   # printf without \n = file with no final newline
    git add config.yaml
    git commit -m "edit config"       # the hook adds a newline, the file changes, the commit is canceled

    Pre-commit runs, adds the newline automatically, marks the file as changed, and cancels the commit. That is normal: the hook made a fix, so now you need a second git add.

    подсказка

    If the commit went through, the hook did not fire; check .pre-commit-config.yaml.

    ✓ pre-commit did its job. The file is fixed and waiting for a re-add.

  6. 06

    Re-commit the already fixed file

    bash
    git add config.yaml           # re-add the file the hook already fixed
    git commit -m "edit config"   # the repeat hook finds no problems, the commit goes through

    pre-commit runs again. The file is already correct, the hook changes nothing, and the commit goes through.

    The main rule is visible in action here: a hook is idempotent, so a second run on a fixed file does nothing.

    ✓ The commit is created. Next, try an explicit rejection.

  7. 07

    Try to commit a file with FIXME and see the rejection

    The third hook (forbid-fixme) is not a fixer, it is a blocker. It does not fix, it only refuses to let the commit through.

    bash
    cd /home/student/work/hooks-lab
    echo "# FIXME refactor later" >> app.py
    git add app.py
    git commit -m "wip: refactor app"

    It should fail with block FIXME in committed code .... Failed. The commit is not created. This is the second pre-commit pattern: some checks cannot be fixed automatically, and the hook simply refuses.

    подсказка

    If the commit went through, either the file has no FIXME or the hook is broken.

    ✓ The hook refused and the commit is blocked. That is the "guard" mode.

  8. 08

    Bypass the hook via --no-verify

    Sometimes you need to record a state urgently without checks. That is --no-verify:

    bash
    git commit --no-verify -m "wip emergency"   # --no-verify skips pre-commit hooks

    The commit went through without running pre-commit. This is an emergency mode, not a habit. In normal work, hooks should fire.

    The rule: --no-verify is for real emergencies (an urgent hotfix where you need to save something first). If you keep catching yourself using --no-verify, fix the hook instead of getting used to bypassing it.

    ✓ You saw the bypass via --no-verify. Now the finale.

  9. 09

    Run pre-commit manually across the whole tree

    When you add pre-commit to an existing repo, it matters to run the hooks over all files at once, not wait for someone to fix them one by one:

    bash
    pre-commit run --all-files    # --all-files = run hooks over the whole tree, not only staged

    pre-commit walks all files, fixes the ones it can fix automatically, and highlights the ones it cannot. You commit it in one PR, "cleanup formatting".

    ✓ pre-commit ran across the whole tree. Lesson complete.

Что ты узнал

pre-commit lives in .pre-commit-config.yaml, is activated by a single command pre-commit install, and catches problems locally before a commit. The main rule: a hook must be fast and idempotent.

команды

  • pre-commit installcreates .git/hooks/pre-commit, registers the framework
  • pre-commit run --all-filesrun all hooks across the whole tree
  • git commit --no-verify -m '...'skip hooks (emergency only)

концепции

  • · hooks are not cloned: a teammate must run `pre-commit install` after a clone
  • · the 5-second rule, or people bypass the hooks
  • · a hook is idempotent: a second run does not change the file

← предыдущая

git worktree: work on two branches in parallel without stash

следующая →

Branch protection and CODEOWNERS in a local forge

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies