In my previous post, I gave the most basic commands to use git and a sample flow to use with GitHub. Today we will see another command and thanks to this command, we will refine the flow to make it better.
It is pretty common that while you do some work, the remote repository you started from (we will call that repository “upstream”) gets some new code. It means that upstream and your local copy have diverged. When you push your local changes to your forked repository (“origin”) and issue a pull request in this state to upstream, there are big chances that a merge will be needed.
The one receiving the pull request (upstream’s owner) might not be interested in doing a long, and painful merge. You have two options:
- Merge your local repository with upstream and push the merge to “origin” so that the pull request is updated
- Rebase your local repository on upstream and force-push the result to “origin” so that the pull request is updated
Rebase consists in replaying your changes on top of the latest version of the upstream repository. So in the end, it looks like your changes were done on top of the latest version of upstream instead of some older version. So you are not diverging anymore, you are just adding on top.
A picture is worth a hundred words:
At t0, both repository are the same. At t1, they have evolved differently. Revision B was done on upstream and revision C was done locally. At t2, after the rebase, we have revision C’ which is C but with B as a parent.
When doing a rebase, you might have to solve some conflicts as you would do for a merge. The big difference is that at the end, only revision C’ is new compared to upstream.
The difference with the merge is pictured right after:
So when doing a pull request after a merge, you send to upstream a much bigger pull request as the common ancestor for your repository and upstream is revision A. In the case of the rebase, the common ancestor is B. And that makes a big difference.
If you want to know how the difference can be, take a look at this on Stackoverflow. So in general, prefer the rebase over the merge.
There is one golden rule though: as you are re-writting the history of the repository when doing a rebase, do not rebase if your changes have already been shared with another repository than origin. The reason why is well explained on the git website.
After you are done with your rebase, you need to push it to origin. A normal push won’t work because you have rewritten the history of your local repository. Pushing would create two heads on the master branch of origin. So you need to force-push to overwrite the history of origin.
Remember that origin should look like:
while you local repository after rebase looks like:
You need to tell git that you know what you are doing by using –force. This will remove revision C and push revision B and revision C’.
Updated GitHub flow
Let’s update the GitHub flow we have seen in the previous article.
- OWNER/REPOSITORY is the repository that you want to contribute to
- YOU/REPOSITORY is your the repository that you obtained by cloning OWNER/REPOSITORY
First step is to clone your forked repository. This is done with:
$ git clone https://github.com/YOU/REPOSITORY REPOSITPORY
Now if you do ‘git remote -v’ in your freshly cloned repository, you will see something like:
origin https://github.com/YOU/REPOSITORY (fetch) origin https://github.com/YOU/REPOSITORY (push)
Add a reference upstream (aka the repository your forked initially) This is done like that:
$ git remote add upstream https://github.com/OWNER/REPOSITORY
So now when you do a ‘git remote -v’ you shall see:
origin https://github.com/YOU/REPOSITORY (fetch) origin https://github.com/YOU/REPOSITORY (push) upstream https://github.com/OWNER/REPOSITORY (fetch) upstream https://github.com/OWNER/REPOSITORY (push)
At this point go code, and commit your changes. As many commits as you wish… When you are done and happy with what you have done, you can push your changes to your forked repository so they can be saved there. It is quite easy to do so, simply do a:
$ git push
You can code, commit and push some more if you want. Eventually, you will want to suggest your changes back to the project you have forked from. This is done with GitHub by submitting a pull request. More here on how to do it
If there are some new revisions in upstream that you did not have when you forked and cloned, the owner of upstream will very likely ask you to rebase. (And in general if upstream has diverged from your local repository, you will want to rebase.)
To do that, first fetch upstream. This will create a read-only branch in your local repository for upstream’s master and any other branch in upstream:
$ git fetch upstream
Now you can rebase. We assume you want to rebase your master on the upstream’s master. It would work the same for another branch. When you are on your master branch, do :
$git rebase upstream/master First, rewinding head to replay your work on top of it... Applying: bla bla bla
If you had several commits, they will all be replayed. Conflicts might happen. So git will pause and ask you to solve the conflicts exactly like it would do for a merge. The only difference is that once you are done solving a conflict instead of running ‘git commit’, you need to run ‘git rebase –continue’.
And when you are happy with it, you can push it to your forked repository by doing a force-push:
$ git push --force origin
Your initial pull request should be updated.