My Git Workflow

It’s been a long while since I published a new entry on this blog, and I keep meaning to improve on that. Today, I was having a conversation with one of my colleagues today and discussing how I set up my local git checkouts. It occurs to me that this might be of interest, so I figure I’ll restart this blog by describing it.

This blog will describe specifically my workflow when dealing with upstreams hosted on Github. There are only minor changes to it when discussing non-Github projects (mainly in the public forking process).

Initial Setup

First, I have to find a project to get involved in. For some people, this is a difficult process involving a great deal of contemplation. For me, on the other hand, I seem to pick up new projects to get involved like they were falling from the sky. Most open-source projects these days seem to be hosted on Github (or at least have a presence there), so my workflow has become reasonably Github-centric.

I will skip over the part where I sign up for a Github account and set up two-factor authentication and uploaded my public SSH key, but rest assured that I have done all of those things. (If you aren’t using two-factor authentication anywhere that is even remotely important, fix that now. I also highly recommend the use of the open-source FreeOTP as a software token for either iOS or Android devices over Google Authenticator; it works anywhere Google Authenticator does.) You may also assume that I am properly logged in to Github at this point.

I’ll use the imaginary package “bar” created by Github user “foo” as my representative example. So I would browse to and then click on the github-fork button. (Since I also belong to several Github organizations, this prompts me for which entity I am cloning to, but if you only have a personal account, it will probably skip this phase).

Local Repository Clone

Now that I have a public fork of the “bar” project, I want to be able to work with it. This means that I need to clone my repository to the local machine so I can operate on its contents. Github provides a handy way to identify the git URL needed for the cloning operation. When cloning my personal Github fork, I will want to clone using the “SSH” URL, which allows both reading from it and pushing changes. (I’ll talk about the “HTTPS” URL in a moment). To find the “SSH” URL, look on the main toolbar of the project’s Github main page. If you don’t see it, check for github-HTTPS and click on it, then select “SSH”. After that, it should look like github-SSH and there will be a URL in the text box to the right of it. It should look something like:

Now we will open a terminal window, change to an appropriate containing directory and run:

git clone

This will pull down a copy of the repository onto the local system, ready to work with. I can make whatever local changes I want and run `git push` to submit them to my public fork. However, we are not finished. The next step will be to create an additional “git remote” that points at the original upstream repository. This I do in order to be able to track other changes that are happening upstream (particularly so I can rebase atop others’ work and ensure that my work still applies atop the upstream code). So in this case, I would do the following: first, I would browse to again and

cd bar.git
git remote add upstream
git remote update

This means that I am adding a new remote name (“upstream”) and associating it with the “HTTPS” (read-only) URL for the original project. (The `git remote update` piece causes me to pull the latest bits from upstream and store them locally.)

Special Case: Upstream Committer

In the special case where I also have commit privileges to the upstream repository, I also add another git remote called “upstream-push” using the “SSH” URL. Then, when I have patches ready to go upstream, I can ready them in a proper branch and then run

git push upstream-push local_branch_name:remote_branch_name

The reason for this additional upstream is to avoid accidental pushes to master (which is very easy to do if you have created a branch from e.g. upstream/remote_branch_name).

Appendix: Useful Git Aliases

In addition to my workflow convention above, I have also created a number of very useful git aliases that I store in my ~/.gitconfig file.

 patch = format-patch -M -C --patience --full-index
 patches = format-patch -M -C --patience --full-index @{upstream}..
 up = remote update
 new = log ..@{upstream}

git patch

This handy alias is essentially a wrapper around `git format-patch`, which is useful for generating comprehensive patch files for `git send-email` (among other uses). It creates patches that auto-detect file copies and renames, diffs using the “patience” algorithm (which tends to be more human-readable than other algorithms) and displays git indices using their un-shortened forms (to avoid ambiguities).

This alias needs to be invoked with additional `git format-patch` arguments, such as a specification of which commits to generate patches. Which leads us to:

git patches

This is a variant of the above, except it will automatically generate all patches on the branch that are not present in the upstream branch being tracked. (So if you are tracking e.g. upstream/master, this will output all of the patches atop master).

git up

This is a quick shorthand for `git remote update` to automatically pull all of the new data from every remote configured in the checkout. This is particularly useful before a `git rebase -i upstream/branch` right before submitting a patch for code-review. (Which you should always do, to make the reviewer’s life easier.)

git new

This is another quick shorthand essentially for a variant of `git log` that only shows you your own commits atop the remote branch.