Back to when I started using Git, I was faced with this challenge. Someone has updated the upstream - how do I incorporate the latest changes into my working branch?
For the sake of simplicity, the upstream will be master
and the working branch working_branch
.
The obvious route is to pull master
and merge it into the working branch. So something like:
Unfortunately, this way we're inevitably creating a new commit.
Working with a team that provides frequent updates while we're slowly working on our feature, we may end up with quite a lot of noise in the git graph.
What we really want in this scenario is the following:
- Isolate the local commits
- Get the latest upstream updates
- Apply the changes on top
- Resolve any conflicts
So we don’t have to merge our branch in the updated upstream but rebase it.
First, let’s fast-forward the local master branch for good measure.
Now let’s head back to our working branch and do a rebase. All of the local commits will be applied on top of the updated master
You can completely ignore updating your local master branch and simply run
git rebase origin/master
.I like having my local master up to date so I opt for the first case.
At this point, everything will either run smoothly or some conflicts will arise.
git rebase --abort
will completely stop the rebase. The branch's initial state will be restored.git rebase --skip
will ignore the local commit that causes the problem - never used it, to be honest- Fix the conflicts, run
git add/rm <files_to_fix>
to mark them as resolved and keep the rebase going withgit rebase --continue
Merging your branch
Having finished the work, it’s always a nice idea to 'squash' commits that are meant to fix previous commits. This way we can automatically generate a Changelog out of the commits and have a tidy git log.
For this reason, doing an 'Interactive' rebase is really helpful
Here’s an example of an interactive rebase prompt. The 'formm' typo is intentional.
Now you can do any of the following
- Pick - Keep the commit
- Reword - Keep the commit, get a prompt to update the message
- Edit - Keep the commit, allows to modify files
- Squash - Merge into the commit above, get a prompt to update the message
- Fixup - Same as
Squash
but won’t prompt for a new message - Exec - Allows running shell commands against a commit
You can reorder these commits around if needed.
For this case, we'll
- Reword and keep the first commit
- Squash the rest and discard the commit message by doing a
Fixup
Consequently, our branch will contain a single meaningful commit on top of all the latest upstream changes.
We push, open a pull request, get some code reviews and when everything is said and done, merge working_branch
into master
.
We only use rebase
to keep our local branch up-to-date. Don’t rebase branches where others are collaborating with you.
Some tips
Backup
It’s always helpful to keep a backup of your working branch if you don’t feel confident about the rebase.
In the case of not having pushed your branch in the remote you can’t do a git reset --hard origin/working_branch
if you mess up, so keep a backup:
Do the rebase process, and if you mess up reset like this:
Rewrite history safely
Say we have opened a pull request but some people have requested changes. We implement them, do another rebase to incorporate the latest changes and do a git push origin working_branch
Unfortunately our changes will be rejected! By doing a rebase, each commit is considered new, and have different hashes. The remote informs you that there are commits (the ones with the old hashes) that are not present in your local branch, so you have to do an update first. That's not the case. We have rewritten history and would rather override everything that is on the remote. Essentially we have to do a force push
.
There is a little caveat with that though. If someone has pushed a commit into the branch before us, and we do a force push - it will forever be gone.
It’s best to do a --force-with-lease
, that will essentially prevent the remote from being updated if someone else has done some work.