A Neat Git(hub) Workflow for Pull Requests

This post is for a friend of mine, so that he can stop bugging me.

I love git and I love Github. Contributing to projects on Github is cool but not always easy if you don’t know where to start. This post presents a neat workflow for handling your Github forks and pull requests.

Assume we forked the cool-people/cool-project repository and that we cloned it on our machine.

Adding the Upstream Remote

First, let’s add a remote for the original repository and call it upstream (you can pick your preferred name here), it will come handy later.

git remote add upstream <address of cool-people/cool-project>

Creating a New Local Branch

As we are git people we don’t develop our new feature/fix/whatever in the master branch, right? Let’s create a branch for our new feature and move to it (name feature can be changed to your favourite name, choose a meaningful name):

git branch feature
git checkout feature

Or directly:

git checkout -b feature

And here we go, writing amazing code.

Handling Conflicts with Upstream’s Master

Once we are done coding and we committed our changes we would like to send our feature back to cool-people/cool-project’s master branch. But what if cool-people/cool-project has gone ahead? We might be in the following situation:

Upstream master branch is ahead of PR with conflicts

And even worse, imagine commits D and E are in conflict with our L, M and N commits. That is, our feature now conflicts with cool-people/cool-project’s master branch and we need to fix this before sending our pull request.

Let’s first update our master branch:

git checkout master
git fetch upstream
git merge upstream/master

We can update our remote master branch as well:

git push origin master

Now that our master branch is up-to-date with cool-people/cool-project’s master we are in the following state:

Our master branch is even with upstream branch

But we haven’t solved our conflict problem yet.
To import master changes into our feature branch and resolve conflicts we use git rebase:

git checkout feature
git rebase master

If there are conflicts bewteen our commits and master’s, the above command will tell us. If that is the case it’s our duty to resolve conflits and git add the files we modified. Once we resolved all conflicts we can continue rebasing with:

git rebase --continue

Rebase does the following:

To better understand this, here’s our history state after rebasing:

Feature branch after rebase

Push our Changes

To update our remote feature branch after rebasing we can use the usual git push. If we have never pushed the branch, we can simply do:

git push origin feature

If we have already pushed it we must force a push, as rebase created non-fast-forward changes (and git by default prevents us from pushig non-fast-forward changes):

git push origin feature -f

We can now go to Github and press the “Compare & pull request” button for the feature branch. This will allow us to finalize our pull request.

Why Rebase?

You might now be asking: why did we use git rebase rather than git merge? The answer to this questions can be found in what merge does to the history, when compared with rebase. Here’s what would have happened to our history if we used merge instead of rebase:

Feature branch after merge

By sending a pull request from our feature branch we are asking cool-people/cool-project to add to their history our commits. Do you think cool-people would ever be interested in adding to their history a merge commit between your branch and their own stuff? I don’t think so.

Rebase, more that merge, can help you send pull requests with a clean history, avoiding commits that add no meaningful information to the history.

Why Not Rebase?

WITH GREAT POWER THERE MUST ALSO COME – GREAT RESPONSIBILITY!

Rebase is a powerfull tool and helps you avoiding dirt in the history. However, as we’ve seen, rebase involves creating non-fast-forward changes. Pushing such changes is never a good idea if your branch is used by other people: rebased commits are new commits, users working on old ones will have a very unpleasent surprise if you rebase their remote branch!

You can safely use rebase when your feature branch is local (i.e. you haven’t pushed it yet), or when you are sure to be the only person working on that branch (which is often the case for pull requests).

comments powered by Disqus