- Introduction
- What is rebasing?
- Scenario 1: The parent branch hasn't changed since you started work and you only have one commit
- Scenario 2: The parent branch hasn't changed but you have multiple commits which need squashing
- Scenario 3: The parent branch has changed and you only have one commit
- Scenario 4: The parent branch has changed and you have multiple commits which need squashing
- Scenario 5: The parent branch has been changed via a force push since you branched off of it
The usual way to work with git is to perform merges and resolve conflicts during the merge. That's fine, but you end up with a commit which looks like:
Merge branch 'develop' into feature/abc-123
And when you look at the content of this commit, it's empty. It's an informational message to tell you that a merge occurred where there may have been conflicts. Specifically, it tells you that since you branched off and started your feature/bug-fix/whatever branch, the parent branch has changed.
However, assuming you don't have any merge conflicts, this merge commit seems redundant and adds noise to your git commit history, which should be a concise, descriptive log of all the work on your project.
It may also be the case that you have multiple commits on your feature branch which were just for you as you worked on the feature and you need to get all of these commits down to one commit before it's merged.
The solution? rebase
.
Rebasing is the concept of taking the commits on your feature branch and replaying them on top of the latest changes on the parent branch.
As each commit is replayed, it might be the case that there are merge conflicts for that particular commit, so you'd resolve them and continue rebasing (read: replaying) your commits. This may result in several rounds of merge conflict resolution. This is an unfortunate side effect of rebasing.
There are two types of rebasing: interactive and non-interactive.
An interactive rebase gives you the opportunity to change commit messages, amalgamate commits (or in git terms, squash) and even ignore certain commits whilst replaying your commits.
This simply replays your commits on top of the rebase target.
Great! No extra work is required. Just ensure you use the command line to merge your work in:
Assuming you're on your feature branch abc-123
:
git checkout develop
git merge abc-123
You should then see something along the lines of:
Updating d2b4f9..37d024
Fast forward
some.src | 1 −
1 files changed, 0 insertions(+), 1 deletions(−)
We need to squash our multiple commits into one commit. To do this, we tell git we want to interactively rebase our work on top of the parent branch.
Assuming the parent branch is develop
and we're on the feature branch abc-123
:
git rebase -i develop
This will present a text editor which looks something like:
pick c3ce1b8e wip
pick b3c91b8f do some stuff
pick b6ce1b9a still doing some stuff
pick 03ce1b9b feature: add a new datepicker
All we want to do here is pick
the first commit and squash
the rest. Don't worry about the commit message at this stage.
pick c3ce1b8e wip
s b3c91b8f do some stuff
s b6ce1b9a still doing some stuff
s 03ce1b9b feature: add a new datepicker
Save that file and close it. It should squash each of the commits into the one commit. This will present another text editor where you can rewrite the commit message for this one commit. Save that file and close it.
Take a look at your git log. You should only see one commit! 🎉
For posterity, and in case you have a pull request sat open in GitHub/GitLab/BitBucket, you must then push your feature branch. However, because you've rewritten your git history, your remote git repository will complain and say your feature branch isn't up to date with what it has.
This requires you to force push. Force pushing is scary and should only be done on your feature branch. In this case, it's good to be very specific with your git commands:
Assuming your remote is named origin
:
git push origin abc-123 --force
This will bring the remote branch up to date with your squashed commits.
Then, it's business as usual:
git checkout develop
git merge abc-123
We need to ensure we have the latest remote parent branch:
git checkout develop
git pull
git reset --hard origin/develop
We then need to replay our changes on top of the up to date parent branch:
git checkout abc-123
git rebase develop
If there are no merge conflicts, great:
git checkout develop
git merge abc-123
If there are, the rebase will pause and you will be given an opportunity to resolve the conflicts. Once the conflicts are resolved, stage the files and continue with the rebase:
git add src.js
git rebase --continue
Then, it's business as usual:
git checkout develop
git merge abc-123
This should only be done after your code has been reviewed so the reviewer can better understand how you've worked.
Follow Scenerio 2 and squash your commits, then follow Scenario 3.
In this case, your feature branch's history prior to your changes is now defunct. It was based off of commits which have subsequently been rewritten.
For this, we have to cherry-pick our commits on top of the new parent.
We need to ensure we have the latest remote parent branch:
git checkout develop
git pull
git reset --hard origin/develop
Now, we want to replay just our changes on top of the new develop
branch. This will require creating a temporary branch whose parent is the new parent branch:
git checkout develop
git checkout -b abc-123-temp
Next, we need to figure out the range of commits we made on our feature branch by switching back to our feature branch, making a note of the commit hash before our first commit and our last commit's hash:
git checkout abc-123
git log
Find your first commit and note the hash of the commit before it (for the sake of referencing, we'll assume this is aaaaaa
), then find your last commit and note its hash (we'll assuming it's bbbbbb
).
Then, switch back to your temporary branch and we'll use the cherry-pick
command to take that range of commits and apply them on this new branch:
git checkout abc-123-temp
git cherry-pick aaaaaa..bbbbbb
Now that the commits have been successfully brought across from your original feature branch, we want to rename our temporary branch to the original feature branch name:
git branch -D abc-123 # Delete the original branch
git checkout -b abc-123 # Create a new branch from our temporary branch with the original name
git branch -D abc-123-temp # Delete the temporary branch
You will need to ensure your force push your feature branch:
git push origin abc-123 --force
Then, it's business as usual:
git checkout develop
git merge abc-123