My ~/.config/git/config
Started circa 2015, last updated • Tagged /practices
I’ve removed the [user] section as irrelevant for anyone else.
Some parts are commented descriptively because I shared it with coworkers long ago.
The aliases are the main reason anyone else might be interested in this.
I think some of them are very good to have.
I use all of the 1–4 letter ones regularly, except su which I haven’t had cause to use for some years,
and id which I had forgotten about (and which I wouldn’t use as much as tip anyway).
I like to rewrite history a lot. I use git-revise for what it can do, and git-rebase for remaining cases.
rf is my favourite invention: add the staged changes into the last commit that changed all of those files (or the specified commit).
[url "git@github.com:"]
insteadOf = "https://github.com/"
# Rule of thumb: use long option names in aliases. Many can be shortened (e.g. --max-count=1 → -1, --interactive → -i) but I prefer to be explicit in scripts and this counts as a script.
[alias]
# I use git via my alias `g`. It’s easy to start typing `g ` and then come back later and type `g foo` instead of just `foo`; so this makes it so that `g g foo` is equivalent to `g foo`. If you don’t use such an alias, go for `git = !git` and `git git foo` will start magically working.
g = !git
# I never, under any circumstances, use `pull`. Ask me why and you’ll be granted a detailed explanation of why `git pull` or `git pull ‹origin›` with no other arguments is terrible (though `pull.ff = only` or `pull.rebase = true` fixes those issues). Remember that `pull` is literally just `fetch` followed immediately by `merge` or `rebase`, anyway. So I just fetch with one of these, and then use `g in` if I feel like looking at what came in, and then use git-merge or git-rebase
f = fetch --prune
fa = fetch --prune --all
fetch-pr = "!f() { if [ $# -ne 2 ]; then >&2 echo \"Usage: git fetch-pr <remote> <pr-number>\"; exit 1; fi; git fetch \"$1\" \"refs/pull/$2/head\"; }; f"
ci = commit
pf = push --force-with-lease
# I only use git-add without --patch for adding new files. Modifications, I always use --patch.
ap = add --patch
# I amend my commits quite a bit. I use these next four very often.
# fixup = "!f() { TARGET=\"$(git rev-parse \"$1\")\"; shift; git commit --fixup=\"$TARGET\" \"$@\" && git -c core.editor=true rebase --interactive --autostash --autosquash \"$TARGET^\"; }; f"
# This one can’t use `-c core.editor=true` because that stops you from editing the commit messages as well. ☹
# squashup = "!f() { TARGET=\"$(git rev-parse \"$1\")\"; shift; git commit --squash=\"$TARGET\" \"$@\" && git rebase --interactive --autostash --autosquash \"$TARGET^\"; }; f"
# With git-revise installed, I can use it for better results:
fixup = revise
squashup = revise -e
# Revise into the commit that last changed File
rf = "!f() { if [ $# -eq 0 ]; then REV=\"$(git status --porcelain --untracked-files=no | sed '/^ /d;s/^.. //' | xargs -n1 git rev-list -1 HEAD -- | uniq)\"; NUM_REVS=\"$(echo \"$REV\" | wc -l)\"; if [ $NUM_REVS -ne 1 ]; then >&2 echo Files in the index were not all last modified in the same commit; exit 1; fi; else REV=\"$(git rev-list -1 HEAD -- \"$1\")\"; shift; fi; git revise \"$REV\" \"$@\"; }; f"
cia = commit --amend --no-edit --reset-author
ciar = commit --amend --reset-author
st = status --short --branch
dc = diff --cached
# I seldom use this, but just occasionally it’s useful.
d = diff --word-diff-regex="[0-9a-zA-Z]+" --word-diff=color
co = checkout
mt = mergetool
su = submodule update --recursive
# This one’s new, I only just added it. I’ll see how I like it. I think I will.
r = rebase --interactive
# These ones are actually a little dangerous because they’re *too* easy for potentially destructive actions: I once typed `g ra` when I meant `g rc`. Just `r = rebase` might be more sensible.
ra = rebase --abort
rc = rebase --continue
rs = rebase --skip
# I thought about `g 🍒` but that’s almost as long as `g cherry-pick` for me to type. `g cherry` is already something else.
pick = cherry-pick
# I almost always use glog rather than log.
glog = log --graph
# “Short log”
slog = log --graph --oneline
# A handy little visualisation here. It’s helpful as a learning exercise to read what all the arguments mean. By the way, if you’re using Git 2.12 or older, you should upgrade, or add log.decorate = auto to this config, or always use --decorate on your git-log invocations. It’s *really* helpful.
history = log --graph --oneline --simplify-by-decoration --all
# The next few commands I cribbed from hg, though the implementation isn’t as easy as I might like! Writing them helped to teach me to use `x..y` refspec syntax more, so that now I comfortably write things like `g glog origin/foo..cmorgan/foo`. There are few things like coming up with ways to do less that teach you more!
# `git id` = `git rev-list --max-count=1` with default refspec HEAD.
id = "!f() { case \"x$1\" in x-*|x) refspec=HEAD;; *) refspec=\"$1\"; shift;; esac; git rev-list --max-count=1 \"$refspec\" \"$@\"; }; f"
# `git tip` = `git log --max-count=1` with default refspec HEAD.
tip = "!f() { case \"x$1\" in x-*|x) refspec=HEAD;; *) refspec=\"$1\"; shift;; esac; git log --max-count=1 \"$refspec\" \"$@\"; }; f"
# `git out` = glog commits that exist locally but not on the upstream branch. No way to refer to different origins, sorry.
out = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git glog \"$branch@{upstream}..$branch\" \"$@\"; }; f"
sout = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git slog \"$branch@{upstream}..$branch\" \"$@\"; }; f"
# `git in` = glog commits that exist on the upstream branch (remember to fetch them first) but not locally.
in = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git glog \"$branch..$branch@{upstream}\" \"$@\"; }; f"
sin = "!f() { case \"x$1\" in x-*|x) branch=;; *) branch=\"$1\"; shift;; esac; git slog \"$branch..$branch@{upstream}\" \"$@\"; }; f"
[pull]
# If you insist on using `git pull`, protect yourself with this (makes --ff-only the default, then you can merge or rebase deliberately) or `rebase = true` (makes --rebase the default).
ff = only
# (Now defaults.)
#[push]
# default = simple
#[color]
# ui = true
[merge]
tool = vimdiff
conflictStyle = diff3
[core]
editor = vim
# I got fed up with being unable to put issue links like “#42” at the start of lines. I also had to modify the gitcommit syntax file in Vim to make this not painful, of course.
# (commit.cleanup = scissors obviates this now, I think, but eh, it’s here now.)
commentchar = ";"
[diff]
algorithm = histogram
[diff "sqlite3"]
# Usage: .gitattributes file in a repository containing `*.sqlite diff=sqlite3`
binary = true
textconv = <<<.dump sqlite3
[rebase]
# This makes --fixup and --squash commits automatically squash themselves appropriately when rebasing interactively. Note the interactive part—`git rebase` *won’t* do this, which I dislike, you’ve got to use --interactive.
autoSquash = true
[stash]
showPatch = true
[mergetool]
keepBackup = false
[commit]
cleanup = scissors
verbose = 1
[advice]
detachedHead = false
[init]
defaultBranch = master