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
Body text: Fonts:
Theme:
Explanation of all this
(yes, this works without JavaScript; persists to cookies)