Git Config & Setup

Git configuration: aliases, hooks, ignore patterns, and repository setup.

Config Fundamentals

# CONFIG LEVELS (lowest to highest priority)
# system:  /etc/gitconfig                 (all users)
# global:  ~/.gitconfig or ~/.config/git/config  (current user)
# local:   .git/config                    (current repo)
# worktree: .git/config.worktree          (current worktree)

# VIEW CONFIG
git config --list                        # All settings, all levels
git config --list --show-origin          # With source file
git config --list --show-scope           # With scope level
git config --list --local                # Repo-only settings
git config --list --global               # User-only settings

# GET SPECIFIC VALUE
git config user.name                     # Current effective value
git config --get-all user.email          # All values (if multiple)
git config --show-origin user.name       # Value + which file set it

# SET VALUES
git config --global user.name "Evan Rosado"
git config --global user.email "evan@example.com"
git config --local core.autocrlf false   # Repo-specific

# UNSET VALUES
git config --global --unset core.editor  # Remove setting
git config --global --unset-all push.default  # Remove all matching

# EDIT CONFIG DIRECTLY
git config --global --edit               # Open global in editor
git config --local --edit                # Open repo config in editor

# CONDITIONAL INCLUDES
# In ~/.gitconfig:
# [includeIf "gitdir:~/work/"]
#     path = ~/.gitconfig-work
# [includeIf "gitdir:~/personal/"]
#     path = ~/.gitconfig-personal
# Different user.email per directory tree

# USEFUL GLOBAL SETTINGS
git config --global init.defaultBranch main
git config --global pull.rebase true
git config --global fetch.prune true
git config --global diff.colorMoved zebra
git config --global rerere.enabled true  # Remember conflict resolutions
git config --global core.editor "nvim"
git config --global merge.conflictstyle diff3

Aliases

# SIMPLE ALIASES
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.sw switch

# LOG ALIASES
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.recent "log --oneline -10"

# DIFF ALIASES
git config --global alias.staged "diff --staged"
git config --global alias.words "diff --word-diff"

# SHELL FUNCTION ALIASES (prefix with !)
# These run in sh, not as git subcommands
git config --global alias.acp '!f() { git add -A && git commit -m "$1" && git push; }; f'
git config --global alias.fresh '!git fetch origin && git reset --hard origin/main'

# COMPLEX ALIASES
# Show branches sorted by last commit
git config --global alias.recent-branches '!git for-each-ref --sort=-committerdate refs/heads/ --format="%(committerdate:relative) %(refname:short)"'

# Quick amend without message change
git config --global alias.amend 'commit --amend --no-edit'

# Undo last commit (keep changes staged)
git config --global alias.undo 'reset --soft HEAD~1'

# LIST ALL ALIASES
git config --get-regexp alias            # Raw
git config --get-regexp alias | awk -F'[. ]' '{printf "%-20s %s\n", $2, substr($0,index($0,$3))}'

# DELETE ALIAS
git config --global --unset alias.old-alias

# ZSH FUNCTION ALTERNATIVES (from .zshrc)
# gach() { git add -A && git commit -m "$(cat)"; }
# gacp() { git add -A && git commit -m "$(cat)" && git push; }
# These are MORE flexible than git aliases for heredoc commits

Hooks

# HOOK LOCATION
# Default: .git/hooks/                   (local, not tracked)
# Shared:  Use core.hooksPath for team-wide hooks

# CONFIGURE HOOKS PATH
git config core.hooksPath .githooks      # Use tracked .githooks/ directory
# Now hooks in .githooks/ apply to this repo

# COMMON HOOKS
# pre-commit:    Runs before commit is created
# commit-msg:    Runs after message entered, before commit finalized
# pre-push:      Runs before push
# post-checkout: Runs after checkout/switch
# post-merge:    Runs after merge

# EXAMPLE: PRE-COMMIT (lint/format check)
cat > .git/hooks/pre-commit << 'HOOK'
#!/bin/bash
# Prevent committing to main directly
branch=$(git symbolic-ref --short HEAD)
if [[ "$branch" == "main" ]]; then
  echo "ERROR: Direct commits to main are not allowed"
  echo "Create a feature branch first"
  exit 1
fi
HOOK
chmod +x .git/hooks/pre-commit

# EXAMPLE: COMMIT-MSG (enforce conventional commits)
cat > .git/hooks/commit-msg << 'HOOK'
#!/bin/bash
msg=$(cat "$1")
pattern='^(feat|fix|docs|style|refactor|test|chore|ci|perf|build)(\(.+\))?: .+'
if ! echo "$msg" | grep -qE "$pattern"; then
  echo "ERROR: Commit message must match conventional commits format"
  echo "Examples: feat(auth): add OAuth support"
  echo "          fix: resolve null pointer in parser"
  echo "          docs(readme): update installation steps"
  exit 1
fi
HOOK
chmod +x .git/hooks/commit-msg

# EXAMPLE: PRE-PUSH (run tests)
cat > .git/hooks/pre-push << 'HOOK'
#!/bin/bash
echo "Running tests before push..."
make test || {
  echo "Tests failed! Push aborted."
  exit 1
}
HOOK
chmod +x .git/hooks/pre-push

# BYPASS HOOKS (use sparingly)
git commit --no-verify -m "emergency fix"
git push --no-verify

# LIST HOOKS
ls -la .git/hooks/                       # Local hooks
ls -la .githooks/ 2>/dev/null            # Shared hooks directory

.gitignore Patterns

# GITIGNORE SYNTAX
# pattern      → ignore matching files
# /pattern     → only in repo root
# pattern/     → only directories
# !pattern     → negate (unignore)
# **/pattern   → any directory depth
# pattern/**   → everything inside

# COMMON PATTERNS
# Build artifacts
*.o
*.pyc
__pycache__/
dist/
build/
*.egg-info/

# IDE/editor
.idea/
.vscode/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

# Secrets (CRITICAL)
.env
.env.*
*.pem
*.key
credentials.json

# GLOBAL GITIGNORE
git config --global core.excludesfile ~/.gitignore_global
# Put OS/editor patterns here, not in project .gitignore

# DEBUG: WHY IS FILE IGNORED?
git check-ignore -v file.txt
# Output: .gitignore:5:*.txt    file.txt
# Shows which rule, which file, which line

# DEBUG: LIST ALL IGNORED FILES
git status --ignored                     # Basic
git ls-files --ignored --exclude-standard  # All ignored files

# FORCE-ADD IGNORED FILE
git add -f secret-but-needed.conf        # Override .gitignore

# STOP TRACKING (but keep file)
git rm --cached file.txt                 # Untrack without deleting
echo "file.txt" >> .gitignore            # Prevent re-adding

# GITIGNORE NOT WORKING? (cached files)
# If file was tracked before .gitignore rule:
git rm -r --cached .                     # Untrack everything
git add .                                # Re-add (respects .gitignore)
git commit -m "chore: apply gitignore rules"
# WARNING: Large commit, every file re-staged

# TEMPLATES
# See: github.com/github/gitignore

.gitattributes

# LINE ENDINGS
# Normalize line endings in repo (LF), convert on checkout
* text=auto                              # Auto-detect text files
*.sh text eol=lf                         # Always LF for shell scripts
*.bat text eol=crlf                      # Always CRLF for batch files
*.adoc text eol=lf                       # AsciiDoc files

# BINARY FILES (don't diff, don't merge)
*.png binary
*.jpg binary
*.pdf binary
*.zip binary

# GIT LFS
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.iso filter=lfs diff=lfs merge=lfs -text

# CUSTOM DIFF DRIVERS
*.adoc diff=asciidoc                     # Better diffs for AsciiDoc

# MERGE STRATEGIES
# Always keep our version of lock files:
package-lock.json merge=ours
yarn.lock merge=ours

# EXPORT IGNORE (excluded from git archive)
.gitignore export-ignore
.gitattributes export-ignore
tests/ export-ignore
docs/ export-ignore

# LINGUIST (GitHub language detection)
docs/**/*.adoc linguist-documentation
vendor/** linguist-vendored

Infrastructure Config Patterns

# DOMUS-* REPO STANDARD CONFIG
# These settings are applied per-repo via .git/config

# Rebase on pull (avoid merge commits)
git config pull.rebase true

# Auto-prune on fetch
git config remote.origin.prune true

# Default push to current branch
git config push.default current

# CONVENTIONAL COMMITS ENFORCEMENT
# Most domus-* repos use this pattern:
# type(scope): description
# Types: feat, fix, docs, chore, refactor, test, ci, style

# MULTI-REMOTE CONFIG
# Standard domus-* remote setup:
git remote add origin git@github.com:EvanusModestus/repo.git
git remote add gitlab git@gitlab.com:EvanusModestus/repo.git

# SSH CONFIG INTEGRATION
# ~/.ssh/config defines Host aliases
# Vault SSH CA signs certificates for auth
# See: secrets-ops docs for vault-ssh-sign workflow

# GITIGNORE FOR DOMUS-* REPOS
# Standard ignores:
# build/          Antora build output
# node_modules/   Antora dependencies
# .env            Local environment
# *.pdf           Generated PDFs (some repos track these)

# VERIFY REPO CONFIG
git config --list --local --show-origin | awk -F'[=\t]' '{printf "%-30s %s\n", $2, $3}'

Config Gotchas

# WRONG: Setting user.name globally when using multiple identities
git config --global user.name "Personal Name"
# Work repos get personal identity!

# CORRECT: Use conditional includes
# ~/.gitconfig:
# [includeIf "gitdir:~/work/"]
#     path = ~/.gitconfig-work
# ~/.gitconfig-work:
# [user]
#     email = evan@work.com

# WRONG: Committing .gitignore changes without clearing cache
echo "secret.env" >> .gitignore
git add .gitignore && git commit -m "ignore secrets"
# secret.env is STILL tracked!

# CORRECT: Remove from index first
git rm --cached secret.env
echo "secret.env" >> .gitignore
git add .gitignore secret.env
git commit -m "chore: stop tracking secret.env"

# WRONG: Hooks not running
# Common causes:
# 1. Not executable
chmod +x .git/hooks/pre-commit
# 2. Wrong shebang
head -1 .git/hooks/pre-commit           # Should be #!/bin/bash

# WRONG: Global gitignore overriding project needs
# Global: *.pdf in ~/.gitignore_global
# But domus-captures tracks PDFs!
# The global rule wins

# CORRECT: Force-add or use project .gitignore to negate
!*.pdf                                   # In project .gitignore

# WRONG: Assuming hooks transfer with clone
git clone repo.git
# .git/hooks/ has only sample files!

# CORRECT: Use core.hooksPath for shared hooks
# In repo: .githooks/ directory (tracked)
# In config: git config core.hooksPath .githooks
# Document in README/CLAUDE.md

Quick Reference

# CONFIG
git config --list --show-origin          # All settings + source
git config --global user.name "Name"     # Set global
git config --local setting value         # Set repo-only
git config --global --edit               # Edit in editor

# ALIASES
git config --global alias.name "command" # Simple alias
git config --global alias.name '!f() { ...; }; f'  # Shell function
git config --get-regexp alias            # List all aliases

# HOOKS
ls .git/hooks/                           # Local hooks
git config core.hooksPath .githooks      # Shared hooks dir
chmod +x .git/hooks/hook-name            # Make executable

# GITIGNORE
git check-ignore -v file.txt             # Debug: why ignored?
git rm --cached file.txt                 # Stop tracking
git add -f file.txt                      # Force-add ignored file

# GITATTRIBUTES
* text=auto                              # Normalize line endings
*.png binary                             # Mark as binary
*.mp4 filter=lfs diff=lfs merge=lfs -text  # Git LFS

See Also

  • Basics — day-to-day commands that config customizes

  • Remotes — multi-remote configuration

  • Filter-Repo — cleaning up after .gitignore failures