A Better Gitconfig
Git provides a rich set of options for customising its behaviour via the .gitconfig
file. I present here some of the more interesting options.
For more comprehensive information on each and every option, you had better peruse the official documentation.
Basics
Depending on your preferences, the global gitconfig file is either located at ~/.gitconfig
, $XDG_CONFIG_HOME/git/config
, or perhaps ~/.config/git/config
— the documentation provides all options.
If you have no existing gitconfig, go ahead and create one in your preferred location. I'm using ~/.config/git/config
.
Includes
You can include other files into your gitignore with the include
directive. This can be useful for colour theme files. Note that files are relative to the current (config) file unless an absolute path is given.
[include]
path = some-file
path = /some/other/file
Per-folder settings / includes
It's also possible to use conditional includes, which lets us enable settings for repos under a particular directory. Suppose, for example, that you use your personal e-mail address for open source work, but want to use your work e-mail for commits to work repositories — or, all repositories under ~/work
.
For this we can use includeIf
:
# ~/.config/git/config
[user]
email = me@mine.com
[includeIf "gitdir:~/work/"]
path = config-work
# ~/.config/git/config-work
[user]
email = me@work.com
# ...
See the docs on conditional includes for more.
Global gitignore
There are some things that you want to make sure never make it into any of your git repos, regardless of how they're each set up, unless you very explicitly force them in. That's why I like to use a global .gitignore
file.
This file defaults to $XDG_CONFIG_HOME/git/ignore
or ~/.config/git/ignore
, but it might make sense to use something under $HOME
for consistency if using ~/.gitconfig
:
# ~/.gitconfig
[core]
excludesfile = ~/.gitignore
# ~/.gitignore
.secrets
*.gpg
.in
# ... etc ...
Parallelization
Some operations can be parallelized, which aren't by default. This might speed up your workflow when working with larger repos, but may also slow you down with smaller repos. YMMV.
[checkout]
workers = -1 ; use all cores to checkout
[fetch]
parallel = 0; reasonable level of parallelization
Conditional tooling
I might work on some machines where one diff or merge tool is available and another isn't. For this reason I like a set of fallbacks to pick from. This can be achieved with simple scripts.
[difftool "difftool"]
cmd = difftool "$LOCAL" "$REMOTE" ; script which selects the right difftool
[diff]
tool = difftool
[mergetool "mergetool"]
cmd = mergetool "$BASE" "$LOCAL" "$REMOTE" "$MERGED" ; script which selects the right mergetool
[merge]
tool = mergetool
Then, somewhere in your path, place the scripts. You'll want to change these depending on your preferred fallback order / installed software:
#!/usr/bin/env bash
#
# difftool
#
# Wraps difftool for git use. Conditionally determined appropriate difftool.
LOCAL="$1"
REMOTE="$2"
if [[ $(command -v "smerge") ]]; then
smerge diff $LOCAL $REMOTE
elif [[ $(command -v "meld") ]]; then
meld "$LOCAL" "$REMOTE"
elif [[ $(command -v "vimdiff") ]]; then
vimdiff "$LOCAL" "$REMOTE"
else
echo "No difftool found."
fi
#!/usr/bin/env bash
#
# mergetool
#
# Wraps mergetool for git use. Conditionally determined appropriate mergetool.
BASE="$1"
LOCAL="$2"
REMOTE="$3"
MERGED="$4"
if [[ $(command -v "smerge") ]]; then
smerge "$BASE" "$LOCAL" "$REMOTE" -o "$MERGED"
if [[ $(command -v "meld") ]]; then
meld --diff "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"
elif [[ $(command -v "vimdiff") ]]; then
vimdiff "$LOCAL" "$MERGED" "$REMOTE"
else
echo "No mergetool found."
fi
Aliases
Finally, what's usually the largest section of one's gitconfig is the aliases. Much like shell aliases, these let us get common operations done with fewer keystrokes and ease the need to remember every switch.
Here's an example:
[alias]
s = status
Now we can use git s
and git will run git status
for us.
Since we all have our preferences, I'll just include my current aliases here for inspiration. I suggest borrowing a couple at a time, lest you end up with a gitconfig full of aliases you've forgotten you have.
[alias]
# add
a = add
aa = add -A
ac = !git add -A && git commit
acm = !git add -A && git commit -m
# branch
b = branch ; list branches
bd = branch -d ; delete
bD = branch -D ; delete (remote)
bmv = branch -m ; rename
; detailed branch list
bl = for-each-ref --sort=committerdate refs/heads/ --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) %(contents:subject) %(color:green)(%(committerdate:relative))%(color:reset) %(color:blue)<%(authorname)>%(color:reset) %(color:yellow)%(objectname:short)%(color:reset)'
# commit
c = commit
cm = commit -m ; inline message
ca = commit --amend ; amend previous commit and edit the message
can = commit --amend --no-edit ; amend previous commit without editing the message
cempty = commit --allow-empty --allow-empty-message -m '' ; make an empty commit with an empty message
# clone
cl = clone
# checkout
co = checkout ; switch branch
cob = checkout -b ; create a new branch
com = !git switch master 2>/dev/null || git switch main ; switch to main or master branch, whichever is present
# cherry-pick
cp = cherry-pick
# diff
d = diff
dc = diff --cached ; diff against staged changes
dh = diff HEAD ; diff against the current head
d1 = diff HEAD^ ; diff against the previous head
d2 = diff HEAD^^ ; diff against the previous head -1
dt = difftool ; open difftool
dta = difftool --dir-diff ; compare directories
# log
ls = log --pretty=doneline ; simple history
lh = log --pretty=doneline --abbrev-commit --stat ; simple history with file changes
ld = log -u ; include diffs
lg = log --graph --pretty=doneline --all ; detailed log with graph
lfs = !git log --pretty=format: --name-only --diff-filter=A | sort -u | grep -v '^$' | fzf -i ; search for files historically
# merge
mt = mergetool
mc = merge --continue
ma = merge --abort
# push
pf = push -u --force-with-lease
pu = push -u
# fetch
prune = fetch --prune
# status
s = status -sbu
staged = diff --name-only --cached ; list of staged files
# stash
sta = stash save --include-untracked ; stash everything; not just staged files.
spop = stash pop
# rebase
r = rebase
rc = rebase --continue
ra = rebase --abort
# rev-parse
sha = rev-parse HEAD ; get current SHA
# reset
rs = reset
rs1 = reset HEAD^
rs2 = reset HEAD^^
rsh = reset --hard
rsh1 = reset HEAD^ --hard
rsh2 = reset HEAD^^ --hard
uncommit = reset --soft HEAD^
# tag
tagd = tag --delete
tagdr = push --delete origin ; remote delete
tags = tag -n --sort=authordate ; list of tags with most recent at the bottom
# etc
cat = show
Bonus
Some extra stuff you might want to consider to improve your git workflow:
- Check out my annotated gitconfig for more ideas
- Use delta as your pager
- Try an interactive CLI for git, such as tig, gitu, or gitui
- Alias
git
tog
in your shell