Skip to main content

Git Configuration: Aliases, Settings, and Hooks

·2007 words·10 mins
Linux Learning Lab
Author
Linux Learning Lab
Writing about code, tools, and workflows.
Table of Contents
git-essentials - This article is part of a series.
Part 5: This Article

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:

LevelFileScope
System/etc/gitconfigAll users on the machine
Global~/.gitconfigYour user account (all repos)
Local.git/configOne 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.email

Setting 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.autocrlf

Editing directly
#

# Open global config in your editor
git config --global --edit

The file is plain INI format:

[user]
    name = Mike
    email = mike@example.com
[core]
    editor = vim
[alias]
    st = status

Essential 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:

EditorSetting
Vimvim
Neovimnvim
VS Codecode --wait
Nanonano
Emacsemacs

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 main

New 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 true

This prevents line-ending conflicts when collaborating across operating systems.

Pull behavior
#

# Rebase instead of merge when pulling
git config --global pull.rebase true

Push 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 true

With 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 mergetool

Color and Output
#

Enable color (usually default)
#

git config --global color.ui auto

Customize 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 short

Now git log shows a compact, colored format by default.

Pager settings
#

# Use less with useful options
git config --global core.pager "less -FRX"
FlagEffect
-FQuit if output fits on one screen
-RShow color codes properly
-XDon’t clear screen on exit

Show branch in status
#

git config --global status.branch true

Aliases
#

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 commit

Now 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 ^alias
alias.st status -sb
alias.lg log --oneline --graph --all --decorate
alias.last log -1 --stat
alias.amend commit --amend --no-edit

Conditional 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.com

Any 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
#

HookRuns whenCommon use
pre-commitBefore commit is createdLint, format, run tests
commit-msgAfter message is writtenValidate message format
pre-pushBefore push to remoteRun full test suite
post-commitAfter commit is createdNotifications
post-mergeAfter a merge completesInstall dependencies
prepare-commit-msgBefore editor opensPre-fill commit template
post-checkoutAfter branch switchRebuild, 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
fi

Make it executable:

chmod +x .git/hooks/pre-commit

If 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
fi

Example: 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
fi

Example: 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
fi

Example: 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
fi

Sharing 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 .githooks

Now 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 test

These 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-verify

Use 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, chore

Every 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-libsecret

For 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 all

merge.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);
>>>>>>> feature

This 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 true

Quick Reference
#

TaskCommand
Set global configgit config --global key value
Set local configgit config --local key value
List all configgit config --list --show-origin
Edit global configgit config --global --edit
Create aliasgit config --global alias.name "command"
Set editorgit config --global core.editor "vim"
Set default branchgit config --global init.defaultBranch main
Set hooks pathgit config core.hooksPath .githooks
Skip hooksgit 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 = auto

Best Practices
#

  • Set your identity globally first — then override per-repo with --local where 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 diff3 conflict 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 -u on first push
  • Store your .gitconfig in a dotfiles repo — makes setup on new machines instant
git-essentials - This article is part of a series.
Part 5: This Article