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.defaultcontrols 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 really really 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.