My Git Config
So let’s start with my standard git configuration. I’ll start with the whole file (well only the interesting bits) and then break down each piece.
Complete Config
[push]
default = upstream
[rebase]
autoSquash = true
autoStash = true
missingCommitsCheck = warn
stat = true
[fetch]
prune = true
[pull]
ff = only
[alias]
alias = config --get-regexp \"^alias\\.\"
exec = "!exec "
co = checkout
sw = checkout
ci = commit
fixup = commit --fixup
amend = commit --amend
st = status
sts = status -s
stsi = status -s --ignored
br = branch
nb = checkout -b
mrg = merge --no-ff --edit
com = checkout master
sm = submodule
ff = merge --ff-only
k = "!exec gitk --all --date-order &"
kf = "!exec gitk --all --first-parent &"
g = "!exec git gui &"
revert = checkout --
fuckit = reset --hard
subup = submodule update --init
gl = log --graph --oneline --author-date-order --decorate=short
gla = log --graph --oneline --author-date-order --decorate=short --all
glf = log --graph --oneline --author-date-order --decorate=short --first-parent
glaf = log --graph --oneline --author-date-order --decorate=short --all --first-parent
glw = log --graph --pretty=format:'%C(auto)%h %C(green)%an %Cblue%ar%Creset%C(auto)%d %s %Creset' --author-date-order --decorate=short
glaw = log --graph --pretty=format:'%C(auto)%h %C(green)%an %Cblue%ar%Creset%C(auto)%d %s %Creset' --author-date-order --decorate=short --all
Configs
Okay, so let’s break it down a bit.
[push]
default = upstream
push.default
controls what happens when you do git push
(with no arguments).
I don’t really like the default (which changed in git v2.0), upstream
tells git to push to the branch’s remote tracking branch (or error out if there isn’t one configured).
I don’t like the other options because my local branches aren’t always named exactly the same as they are on the server.
One reason for this is our centralized flow at $work where server branches were named $user/$topic
so that everybody’s branches were separated. But I didn’t like having my username on my local branches, thus my local branch foo
would be called immute/foo
on the server.
[rebase]
autoSquash = true
autoStash = true
missingCommitsCheck = warn
stat = true
Autostash is pretty easy to explain. Whenever you do a git rebase
it will automatically stash unstaged changes, and then it will automatically unstash them after the rebase is complete. Super convenient.
Autosquash is a little tricker to explain, so lets start with an example. Suppose you’re working along, committing frequently like you should. Then you realize you have something you want in a previous commit. If it’s the latest commit you want added to, it’s really simple: stage those changes and then git commit --amend
. But if it’s an earlier commit, it’s tricker - luckily Git has some help!
Suppose you want to add stuff to $COMMIT_SHA
, simply stage them like you would for an amend, but instead run git commit --fixup $COMMIT_SHA
. This will add a new commit with a message that is the same as $COMMIT_SHA
but with “!fixup” prepended to it.
Now if you do an interactive rebase with git rebase --autosquash --interactive
, Git will “notice” that fixup commit and automatically move it right after $COMMIT_SHA
and change it to the “fixup” command (instead of the “pick” command).
Similarly, you can use git commit --squash
to have it automatically set to the “squash” command.
The rebase.autoSquash
config simply makes --autosquash
the default for git rebase
. This is super useful and I can’t imagine why it’s no the default.
rebase.missingCommitsCheck
can be used to warn the user (or error out) if they drop commits from an interactive rebase (instead of using the “drop” command).
I recommend setting this to error
to catch mistakes. I keep it on warn
because I do this intentionally pretty frequently and don’t like having to edit text instead of just removing the line with ^K. I keep warn, though, as a sanity check while the rebase runs.
rebase.stat
adds a diffstat of what changed upstream since the last rebase. I used this frequently at one point, so it got turned on.
[fetch]
prune = true
[pull]
ff = only
fetch.prune
tells git to use --prune
when you git fetch
. This cleans up stale tracking branches of branches that have been removed from the server.
Normally, git pull
is essentially equivalent to git fetch
followed by git merge
. This will result in either a merge commit or a fast-forward, depending on if the branches have diverged.
If you’re like me, you hate it when these merge commits litter the history. I never actually use git pull
, but if I slip I want to make sure it only fast-forwards. Then I can decide if I want to merge or do a rebase.
Aliases
Now comes the fun part. I have a ton of aliases, roughly in two categories: command shortening and useful things.
[alias]
sm = submodule
com = checkout master
co = checkout
sw = checkout
ci = commit
fixup = commit --fixup
amend = commit --amend
st = status
sts = status -s
stsi = status -s --ignored
br = branch
nb = checkout -b
revert = checkout --
Most of these should be pretty apparent what they do - they’re just shorter for less typing.
st
, sts
, and stsi
are all shorthands for git status
, first the long and short forms, then the short form but including ignored files.
fixup
and amend
are useful for fixing up recent-ish commits - see above in the rebase
section.
revert
makes more sense to my brain (especially after coming from SVN) for throwing away uncommitted changes to a file.
mrg = merge --no-ff --edit
ff = merge --ff-only
I use git mrg
for when I absolutely want to make a merge commit: --no-ff
forces a merge commit even if it could fast-forward and --edit
drops me into the editor for the commit message.
Similarly, I use git ff
when I think I should be able to fast-forward. I really like having control over what Git is doing and I despise random merge commits in my history. It’s partly an aesthetic thing, but it’s mostly about going back through those commits - programming should be a craft, not something that is done sloppily.
fuckit = reset --hard
This one is my favorite. The name of the alias is the mood you need to be in to use it.
In a weird merge that is all fucky and you just want to throw it all away? git fuckit
!
Have a bunch of printf()
changes laying around after committing all the useful code? git fuckit
!
This alias will revert all uncommitted changes, so use it carefully.
subup = submodule update --init
Submodules are among the worst UX of the Git CLI. We used this one a ton at $work so an alias made it faster.
exec = "!exec"
Normally, Git aliases must “resolve” to a git command. Given an alias like fixup = commit --fixup
, if you run git fixup
it’ll be as if you ran git commit --fixup
.
Aliases that are prefixed with a !
are instead handed off to the shell for execution. So if you have an alias like root-dir = !exec pwd
, if you run git root-dir
it will instead execute pwd
. The neat thing about this is Git will execute the command with the current working directory set to the root of the repo.
So in that pwd example, git pwd
will print the path to the root of the repo.
k = "!exec gitk --all --date-order &"
kf = "!exec gitk --all --first-parent &"
g = "!exec git gui &"
Various short ways to invoke git-gui
and gitk
.
I’m not sure why these use !exec ...
instead of just !...
. I think it pairs with the exec alias so that they get started in the root of the repo rather than wherever I happen to be at the time. I’ll have to figure this out.
gl = log --graph --oneline --author-date-order --decorate=short
gla = log --graph --oneline --author-date-order --decorate=short --all
glf = log --graph --oneline --author-date-order --decorate=short --first-parent
glaf = log --graph --oneline --author-date-order --decorate=short --all --first-parent
glw = log --graph --pretty=format:'%C(auto)%h %C(green)%an %Cblue%ar%Creset%C(auto)%d %s %Creset' --author-date-order --decorate=short
glaw = log --graph --pretty=format:'%C(auto)%h %C(green)%an %Cblue%ar%Creset%C(auto)%d %s %Creset' --author-date-order --decorate=short --all
Now come the logging aliases.
gl
, gla
, glf
, and glaf
are all variations on the same base. The base is to show the history with one commit per line and a semi-graphical view of the commit graph (like gitk).
gla
adds --all
so that every branch is shown (instead of just the current branch with gl
).
glf
adds --first-parent
so that topic branches are only shown as the commit that merged them into trunk.
glaf
is the combination of the two.
glw
is one I got from a coworker. It’s the same graphical view, but it shows the author and relative timestamp in addition to the commit SHA.
glaw
simply adds --all
.