Why Configure Git?#
Git works fine out of the box, but a few minutes of configuration makes your daily experience noticeably better. You can shorten common commands to a few characters, set sensible defaults, colorize output, choose your preferred editor and merge tool, and run scripts automatically on commits and pushes.
This post covers the configuration system, the most useful settings to change, how to build aliases that save real time, and how hooks let you automate quality checks.
Configuration Levels#
Git has three configuration levels. Each one overrides the one above it:
| Level | File | Scope |
|---|---|---|
| System | /etc/gitconfig | All users on the machine |
| Global | ~/.gitconfig | Your user account (all repos) |
| Local | .git/config | One specific repository |
Most personal configuration goes in global. Project-specific overrides go in local.
Viewing configuration#
# Show all settings and where they come from
git config --list --show-origin
# Show just global settings
git config --global --list
# Show just local (repo) settings
git config --local --list
# Get a specific value
git config user.emailSetting values#
# Global (applies to all repos)
git config --global user.name "Mike"
git config --global user.email "mike@example.com"
# Local (this repo only)
git config --local user.email "mike@work.com"Unsetting values#
git config --global --unset core.autocrlfEditing directly#
# Open global config in your editor
git config --global --editThe file is plain INI format:
[user]
name = Mike
email = mike@example.com
[core]
editor = vim
[alias]
st = statusEssential Settings#
Identity#
git config --global user.name "Mike"
git config --global user.email "mike@example.com"Every commit records these. Set them once globally, override per-repo if you use different identities for work and personal projects.
Default editor#
git config --global core.editor "vim"This is used for commit messages, interactive rebase, and anywhere Git needs you to edit text. Common choices:
| Editor | Setting |
|---|---|
| Vim | vim |
| Neovim | nvim |
| VS Code | code --wait |
| Nano | nano |
| Emacs | emacs |
The --wait flag for VS Code tells Git to wait until you close the editor tab before continuing.
Default branch name#
git config --global init.defaultBranch mainNew repos created with git init will use main instead of master.
Line endings#
# On Linux/Mac — commit LF, check out LF
git config --global core.autocrlf input
# On Windows — commit LF, check out CRLF
git config --global core.autocrlf trueThis prevents line-ending conflicts when collaborating across operating systems.
Pull behavior#
# Rebase instead of merge when pulling
git config --global pull.rebase truePush behavior#
# Only push the current branch (not all branches)
git config --global push.default current
# Auto-set upstream on first push
git config --global push.autoSetupRemote trueWith push.autoSetupRemote, you can skip the -u flag — git push on a new branch automatically creates the remote tracking branch.
Diff and merge tools#
# Use vimdiff for diffs and merges
git config --global diff.tool vimdiff
git config --global merge.tool vimdiff
# Or use VS Code
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'Launch them with:
git difftool
git mergetoolColor and Output#
Enable color (usually default)#
git config --global color.ui autoCustomize log format#
git config --global format.pretty "format:%C(yellow)%h%C(reset) %C(cyan)%ad%C(reset) %s %C(green)(%an)%C(reset)%C(red)%d%C(reset)"
git config --global log.date shortNow git log shows a compact, colored format by default.
Pager settings#
# Use less with useful options
git config --global core.pager "less -FRX"| Flag | Effect |
|---|---|
-F | Quit if output fits on one screen |
-R | Show color codes properly |
-X | Don’t clear screen on exit |
Show branch in status#
git config --global status.branch trueAliases#
Aliases let you create shorthand commands. They’re defined in your config and run as if you typed the full command.
Creating aliases#
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commitNow git st runs git status, git co main runs git checkout main, etc.
Useful aliases#
# Status
git config --global alias.st "status -sb"
# Compact log
git config --global alias.lg "log --oneline --graph --all --decorate"
# Last commit
git config --global alias.last "log -1 --stat"
# Amend without editing message
git config --global alias.amend "commit --amend --no-edit"
# Unstage a file
git config --global alias.unstage "restore --staged"
# Diff of what's staged
git config --global alias.staged "diff --staged"
# List aliases
git config --global alias.aliases "config --get-regexp ^alias"
# Show current branch name
git config --global alias.current "branch --show-current"
# Pull with rebase
git config --global alias.up "pull --rebase"
# Delete merged branches
git config --global alias.cleanup "!git branch --merged main | grep -v 'main' | xargs -r git branch -d"Shell command aliases#
Prefix with ! to run a shell command instead of a git subcommand:
# Open the repo in the browser (GitHub)
git config --global alias.web "!gh browse"
# Show contributor stats
git config --global alias.contributors "!git shortlog -sn --all"
# Find large files in history
git config --global alias.largest "!git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sed -n 's/^blob //p' | sort -rnk2 | head -10"Viewing all aliases#
git config --get-regexp ^aliasalias.st status -sb
alias.lg log --oneline --graph --all --decorate
alias.last log -1 --stat
alias.amend commit --amend --no-editConditional Configuration#
Use different settings for different directories — useful when you have work and personal projects:
# ~/.gitconfig
[user]
name = Mike
email = mike@personal.com
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work# ~/.gitconfig-work
[user]
email = mike@company.comAny repo under ~/work/ automatically uses your work email. Everything else uses your personal email.
Git Hooks#
Hooks are scripts that Git runs automatically at specific points in the workflow. They live in .git/hooks/ and are named after the event they respond to.
Available hooks#
| Hook | Runs when | Common use |
|---|---|---|
pre-commit | Before commit is created | Lint, format, run tests |
commit-msg | After message is written | Validate message format |
pre-push | Before push to remote | Run full test suite |
post-commit | After commit is created | Notifications |
post-merge | After a merge completes | Install dependencies |
prepare-commit-msg | Before editor opens | Pre-fill commit template |
post-checkout | After branch switch | Rebuild, clean caches |
Creating a hook#
Hooks are executable scripts. Create them in .git/hooks/:
#!/bin/bash
# .git/hooks/pre-commit
# Run linter before allowing commit
npm run lint
if [ $? -ne 0 ]; then
echo "Lint failed. Fix errors before committing."
exit 1
fiMake it executable:
chmod +x .git/hooks/pre-commitIf the script exits with a non-zero status, Git aborts the operation.
Example: Prevent commits to main#
#!/bin/bash
# .git/hooks/pre-commit
branch=$(git branch --show-current)
if [ "$branch" = "main" ]; then
echo "Direct commits to main are not allowed. Use a feature branch."
exit 1
fiExample: Validate commit message format#
#!/bin/bash
# .git/hooks/commit-msg
commit_msg=$(cat "$1")
# Require conventional commit format
if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+"; then
echo "Commit message must follow conventional commits format:"
echo " feat(scope): description"
echo " fix: description"
echo ""
echo "Got: $commit_msg"
exit 1
fiExample: Auto-install dependencies after merge#
#!/bin/bash
# .git/hooks/post-merge
# Check if package.json changed
changed_files=$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)
if echo "$changed_files" | grep -q "package.json"; then
echo "package.json changed — running npm install..."
npm install
fiExample: Run tests before push#
#!/bin/bash
# .git/hooks/pre-push
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fiSharing hooks with a team#
Hooks in .git/hooks/ aren’t committed (.git/ is not tracked). To share hooks:
Option 1: Custom hooks directory
# Store hooks in the repo
mkdir .githooks
# Tell Git to look there
git config core.hooksPath .githooksNow commit .githooks/ and everyone gets the same hooks after running that config command.
Option 2: Use a tool
Tools like Husky (Node.js) or pre-commit (Python) manage hooks through config files that live in the repo:
# .husky/pre-commit
npm run lint
npm testThese install the actual hooks automatically when developers run npm install.
Skipping hooks temporarily#
git commit --no-verify -m "WIP: skip hooks for now"
git push --no-verifyUse sparingly — hooks exist for a reason.
Commit Templates#
Set a default template that pre-fills the commit message editor:
git config --global commit.template ~/.gitmessage# ~/.gitmessage
# <type>(scope): subject
#
# Body: explain what and why (not how)
#
# Footer: references, breaking changes
#
# Types: feat, fix, docs, style, refactor, test, choreEvery time you run git commit (without -m), this template appears in your editor.
Credential Storage#
Avoid typing passwords for HTTPS remotes:
# Cache credentials in memory for 1 hour
git config --global credential.helper 'cache --timeout=3600'
# Store permanently in a file (plaintext — less secure)
git config --global credential.helper store
# Use the system keychain (recommended on macOS)
git config --global credential.helper osxkeychain
# Use the system keychain (Linux with libsecret)
git config --global credential.helper /usr/lib/git-core/git-credential-libsecretFor most workflows, SSH keys are simpler and more secure than HTTPS credentials.
Useful Miscellaneous Settings#
# Show original version in merge conflicts (helps understand context)
git config --global merge.conflictstyle diff3
# Automatically clean up remote-tracking branches on fetch
git config --global fetch.prune true
# Reuse recorded resolution (remember how you resolved conflicts)
git config --global rerere.enabled true
# Sign commits with GPG (if you have a key configured)
git config --global commit.gpgsign true
git config --global user.signingkey YOUR_KEY_ID
# Show untracked files in subdirectories (more detail in status)
git config --global status.showUntrackedFiles allmerge.conflictstyle diff3#
The default conflict view shows two sides. diff3 adds the common ancestor — the original version before either branch changed it:
<<<<<<< HEAD
return cache.get(id);
||||||| common ancestor
return database.find(id);
=======
return database.findById(id);
>>>>>>> featureThis makes it much easier to understand what each side intended to change.
rerere — Reuse Recorded Resolution#
When enabled, Git remembers how you resolved a conflict. If the same conflict appears again (common during rebases), Git resolves it automatically.
git config --global rerere.enabled trueQuick Reference#
| Task | Command |
|---|---|
| Set global config | git config --global key value |
| Set local config | git config --local key value |
| List all config | git config --list --show-origin |
| Edit global config | git config --global --edit |
| Create alias | git config --global alias.name "command" |
| Set editor | git config --global core.editor "vim" |
| Set default branch | git config --global init.defaultBranch main |
| Set hooks path | git config core.hooksPath .githooks |
| Skip hooks | git commit --no-verify |
A Complete Starter Configuration#
Here’s a practical ~/.gitconfig combining the most useful settings from this post:
[user]
name = Your Name
email = you@example.com
[core]
editor = vim
pager = less -FRX
autocrlf = input
[init]
defaultBranch = main
[pull]
rebase = true
[push]
default = current
autoSetupRemote = true
[fetch]
prune = true
[merge]
conflictstyle = diff3
[rerere]
enabled = true
[status]
branch = true
showUntrackedFiles = all
[alias]
st = status -sb
co = checkout
br = branch
ci = commit
lg = log --oneline --graph --all --decorate
last = log -1 --stat
amend = commit --amend --no-edit
unstage = restore --staged
staged = diff --staged
up = pull --rebase
aliases = config --get-regexp ^alias
[color]
ui = autoBest Practices#
- Set your identity globally first — then override per-repo with
--localwhere needed (work vs personal email) - Use conditional includes for work/personal separation — cleaner than remembering to set local config in every repo
- Build aliases gradually — add one when you notice you type the same command repeatedly, not all at once
- Use
diff3conflict style — the common ancestor makes conflicts much easier to understand - Enable
rerere— it saves you from re-resolving the same conflict during rebases - Enable
fetch.prune— stale remote-tracking branches are confusing; prune them automatically - Share hooks via
core.hooksPath— don’t rely on everyone manually installing hooks - Keep hooks fast — a pre-commit hook that takes 30 seconds will make people skip it with
--no-verify - Use
push.autoSetupRemote— eliminates the need to remember-uon first push - Store your
.gitconfigin a dotfiles repo — makes setup on new machines instant


