Git: Hooks, Attributes, and Config
Git has a rich configuration system for customizing behaviour at every level. Hooks are scripts that run automatically at specific points in the Git workflow. Attributes define per-path settings for things like line endings, diff output, and merge strategies. The .gitignore and .mailmap files further control what Git tracks and how it labels contributors.
Commands Covered
git hook · git config (advanced) · git check-attr · git check-ignore · git check-mailmap · git interpret-trailers
git hook
Hooks are shell scripts in .git/hooks/ that Git executes automatically at defined points. They can validate commits, run tests, enforce style, or notify services.
# List available hooks $ git hook list pre-commit # Run a hook manually $ git hook run pre-commit # Hook directory $ ls .git/hooks/ # applypatch-msg.sample pre-commit.sample # commit-msg.sample pre-push.sample # post-commit.sample prepare-commit-msg.sample # ... # Enable a hook: remove the .sample extension and make it executable $ cp .git/hooks/pre-commit.sample .git/hooks/pre-commit $ chmod +x .git/hooks/pre-commit
Common Hooks
| Hook | When it runs | Common uses |
|---|---|---|
pre-commit | Before commit message is entered | Run linter, tests; reject if they fail |
commit-msg | After message is written, before commit | Enforce message format; check for ticket numbers |
prepare-commit-msg | Before editor opens for message | Pre-fill commit message with branch name or template |
post-commit | After commit is created | Notifications; local CI triggers |
pre-push | Before push to remote | Run test suite; block push on failure |
pre-rebase | Before rebase starts | Warn about rebasing shared branches |
post-merge | After a successful merge | Run dependency install if lockfile changed |
post-checkout | After switching branches | Rebuild, update dependencies |
# Example pre-commit hook — run PHP syntax check:
#!/bin/sh
git diff --cached --name-only --diff-filter=ACM | grep '\.php$' | while read f; do
php -l "$f" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "PHP syntax error in $f"
exit 1
fi
done
# Example commit-msg hook — enforce prefix format:
#!/bin/sh
MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|refactor|test|chore): .+"
if ! echo "$MSG" | grep -qE "$PATTERN"; then
echo "Commit message must start with: feat|fix|docs|refactor|test|chore: ..."
exit 1
fi
# Skip hooks temporarily (e.g. emergency commit):
$ git commit --no-verify -m "Emergency fix"
# Sharing hooks with the team — hooks in .git/hooks/ are not committed.
# Options:
# 1. Store hooks in a repo directory and symlink or copy them.
# 2. Use git config core.hooksPath to point to a committed directory:
$ git config core.hooksPath .githooks
# Then commit .githooks/pre-commit to the repo.
git config (Advanced)
# Config levels (each overrides the previous): # --system /etc/gitconfig (all users on machine) # --global ~/.gitconfig or ~/.config/git/config (your account) # --local .git/config (this repo only) # --worktree .git/config.worktree (specific worktree) # Show where a setting comes from $ git config --list --show-origin --show-scope # Unset a config value $ git config --unset user.email $ git config --global --unset core.editor # Edit config file directly $ git config --global --edit # Useful settings: $ git config --global core.autocrlf input # LF on commit, leave alone on checkout (Mac/Linux) $ git config --global core.autocrlf true # CRLF on checkout, LF on commit (Windows) $ git config --global core.whitespace trailing-space,space-before-tab $ git config --global push.default current # push current branch to same-named remote branch $ git config --global fetch.prune true # always prune on fetch $ git config --global merge.conflictstyle diff3 # show the common ancestor in conflicts $ git config --global rebase.autoStash true # auto-stash before rebase $ git config --global log.date relative # show "2 hours ago" in log # Conditional config — use different email for work repos $ git config --global includeIf.gitdir:/home/user/work/.path work.gitconfig # In ~/work.gitconfig: # [user] # email = jason@company.com
git check-ignore
Debugs which .gitignore rule caused a file to be ignored.
# Check why a file is ignored $ git check-ignore -v build/output.log # .gitignore:5:build/ build/output.log # Shows: filename:line:pattern matched-file # Check a file that isn't ignored (verbose shows nothing is matching) $ git check-ignore -v src/main.c # (no output = not ignored) # Check multiple files $ git check-ignore -v *.log # Check even if the file doesn't exist on disk $ git check-ignore --non-matching -v hypothetical.pyc # List all ignored files $ git ls-files --ignored --exclude-standard
git check-attr
Shows which .gitattributes rules apply to files.
# Check a specific attribute $ git check-attr text README.md # README.md: text: auto # Check all attributes for a file $ git check-attr -a src/image.png # src/image.png: text: unset # src/image.png: binary: set # src/image.png: diff: unset # src/image.png: merge: unset # Example .gitattributes for a PHP project: # * text=auto # auto-detect text files, normalize line endings # *.php text eol=lf # PHP files always use LF # *.sh text eol=lf executable # shell scripts: LF + mark executable # *.png binary # binary — don't diff or convert # *.jpg binary # *.pdf binary # *.sql diff=sql # use sql diff driver # CHANGELOG.md merge=union # merge conflicts: keep both sides
git check-mailmap
Shows canonical names and emails for contributors. .mailmap lets you consolidate different author names and emails (someone commits from different machines with different configs) into a single identity for log and shortlog output.
# Check how an author appears after mailmap is applied $ git check-mailmap "Old Name <old@email.com>" # Example .mailmap file: # Canonical Name <canonical@email.com> <old@email.com> # Jason Smith <jason@dispelled.ca> <jason@oldaddress.com> # Jason Smith <jason@dispelled.ca> Jason Old-Lastname <jason@oldaddress.com> # After mailmap, git shortlog -sn shows the canonical identity $ git shortlog -sn
git interpret-trailers
Adds or parses structured key: value lines (trailers) in commit messages. Trailers are a convention for adding metadata like "Signed-off-by", "Co-authored-by", "Closes", "Reviewed-by".
# Add a trailer to a commit message
$ git interpret-trailers --trailer "Signed-off-by: Jason <jason@dispelled.ca>" \
--in-place .git/COMMIT_EDITMSG
# Parse trailers from the last commit
$ git log -1 --format="%B" | git interpret-trailers --parse
# Amend the last commit with a new trailer
$ git commit --amend --trailer "Reviewed-by: Alice <alice@example.com>"
# Example commit message with trailers:
# Fix null pointer in session handler
#
# The session object was not checked for null before calling getUser().
# Closes #142
#
# Signed-off-by: Jason <jason@dispelled.ca>
# Co-authored-by: Alice <alice@example.com>
dispelled