Stupid Git Tricks — or — Make Your Revision Control System Work For You

I spend a fair amount of my day interacting with git. I keep operational configurations (i.e. BOSH deployments) in git. I keep all of my code in git. Hell, this blog is even in git (as is the software that runs it).

In my consulting work, I've introduced git to hundreds of people and dozens of teams, and here's what I've learned:

You need to customize git just a little.

gitprompt

This is what my prompt looks like, halfway through writing this essay:

Everything before the ) is from a little utility called gitprompt. It's made up of the following pieces:

I find it useful to know, at a glance, what branch I'm on, and having the HEAD commit ID right there in the prompt has come in handy on more than one occasion - "Are you sure you've done a git pull? I'm at 72b147a on the fix-things branch..."

Above all else, I use that working copy state more than anything else. It's really the output of a git status, condensed down to a series of single-character flags and numbers. In the above example, +1 indicates that one file has been staged for commit (via git add), the *2 shows me that two tracked files have unstaged changes, and the f4 means there are four new (untracked) files that are not gitignore'd.

This often saves me from running a git status.

To use gitprompt, you'll have to download a copy to ~/bin, and add the following to your ~/.bashrc:

type git >/dev/null 2>&1
if [[ $? == 0 ]]; then
  export PS0="%{%[\e[1;34m%]%b%[\e[00m%]:%[\e[1;33m%]%i%[\e[00m%]%}%{%[\e[1;31m%]%c%u%f%t%[\e[00m%]) %}$PS1 "
  export PROMPT_COMMAND='export PS1=$($HOME/bin/gitprompt c=\+ u=\* statuscount=1)'
fi

(PROMPT_COMMAND is run before every prompt; in this configuration, gitprompt consumes the PS0 environment variable, interprets the parts between %{...}% and then sets PS1 accordingly. Neat, huh?)

gitconfig

Git itself is surprisingly easy to configure. While you can run the git-config ... command to set everything, it's easier to just edit your /.gitconfig by hand. Here's (part of) mine:

[user]
  name = James Hunt
  email = ...

[core]
  excludesFile = ~/.gitignore

[push]
  default = simple

[color]
  ui = auto
  diff = auto

[color "diff"]
  new = green bold
  old = red bold
  meta = white
  func = magenta bold
  frag = yellow bold
  whitespace = blue reverse

[color "status"]
  added = green bold
  changed = red bold
  untracked = cyan bold

The [user] section is all about you. This is where you set your display name and email address, as they will appear in commit messages you author.

The [core] section customizes the core behavior of git itself. I use a global .gitignore file for common things that I know I never want committed, like object code files (*.o), backup files (*~ and *.bak), etc.

The [push] section manages how git push behaves. Setting the default push strategy to simple accepts the default behavior of Git 2.0+, is the safest strategy that causes the least amount of mayhem, and is generally suited to all workflows. See git-config(1) for more details.

The [color*] sections govern terminal colorization. I am a visual person, and I like to see things in terms of color as well as text. Out of the box, commands like git diff and git status don't take advantage of modern terminal emulators and print everything in the default colors.

I should point out that I use white foreground text on a black background, and my color choices arise from that aesthetic. If you prefer black-on-white, you probably want to pick a different color scheme.

Here's what git status looks like, all colorized:

At a glance, I can tell if there's anything changed (in red), anything to commit (in green) and anything new (light blue).

git alias

My ~/.gitconfig also contains my git aliases. These aliases result from years of using git, and have been lovingly crafted and tweaked over thousands if not millions of invocations of git.

[alias]
  st = status
  ci = commit
  br = branch
  co = checkout
  df = diff
  dfc = diff --cached
  dfp = diff --stat -p
  who = shortlog -s --
  lg = log -n 20 --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset %an' --abbrev-commit --date=relative

The first five are simple elisions for the common set of commands. I type run git status far too often to have to type those additional four keystrokes.

NOTE: I have met people who define even shorter shell aliases, like gs='git status' or gl='git log'. I work with people who have aliases like gcam='git commit -a -m'. I don't do that because it obscures the fact that the command being called is in fact git. Do what works for you.

Looking at diffs is a fundamental git thing, so I give git diff its own alias. I also define two variations on the base diff: git dfc and git dfp.

git dfc shows the difference between what's been staged and what's been committed. I use this regularly when crafting commit messages, to make sure that what I think I'm committing is what I'm actually committing, and that the changes staged are coherent and related.

git dfp is a regular diff, but adds a small header listing files, the number of lines added / removed, and a textuo-visual representation of the change.

Here's an example:

git who is something I use to get a feel for contributor share, i.e. who has the most code / commits and therefore may be best to approach with questions, thoughts and ideas. This isn't always correct — Go repositories in particular skew towards whoever introduces the most voluminous project dependencies, but it's a start.

Finally, git lg is my favorite. It's an elision of git log that saves exactly one character, until you realize that there's a ton of options in that alias definition. Those options make git lg unique enough in its own right. Here's an example:

Here's what I like most about git lg:

  1. Commit IDs (abbreviated) are listed (in red)
  2. Branch and Tag information is present (in yellow)
  3. Commit message summaries are displayed
  4. Commit dates (in green) are relative to today
  5. Author names (but not emails) are visible
  6. It's compact

The great thing about all of these aliases is that they are partial. You can give them arguments and everything works as expected:

git df origin/branch-name
git ci that/stuff
git lg origin/master..HEAD

etc.

Conclusion

If you spend a measurable amount of your day interacting with git, you owe it to yourself to customize your environment to make life easier. I hope you can find some value in my configuration and aliases, but more than that, I hope you go out, read the man pages, play with the knobs and levers, and find something that works for you.

Happy Hacking!

James (@iamjameshunt) works on the Internet, spends his weekends developing new and interesting bits of software and his nights trying to make sense of research papers.

Currently working on Rook.