Skip to content

Instantly share code, notes, and snippets.

@yoitsro
Last active January 12, 2024 07:31
Show Gist options
  • Save yoitsro/4853816a11848a77ed563d2a73a254f2 to your computer and use it in GitHub Desktop.
Save yoitsro/4853816a11848a77ed563d2a73a254f2 to your computer and use it in GitHub Desktop.
A guide to git rebasing

Rebasing with Ro

Contents

  1. Introduction
  2. What is rebasing?
  3. Scenario 1: The parent branch hasn't changed since you started work and you only have one commit
  4. Scenario 2: The parent branch hasn't changed but you have multiple commits which need squashing
  5. Scenario 3: The parent branch has changed and you only have one commit
  6. Scenario 4: The parent branch has changed and you have multiple commits which need squashing
  7. Scenario 5: The parent branch has been changed via a force push since you branched off of it

Introduction

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.

What is rebasing?

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.

Interactive rebasing

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.

Non-interactive rebasing

This simply replays your commits on top of the rebase target.

Scenario 1: The parent branch hasn't changed since you started work and you only have one commit

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(−)

Scenario 2: The parent branch hasn't changed but you have multiple commits which need squashing

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

Scenario 3: The parent branch has changed and you only have one commit

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

Scenario 4: The parent branch has changed and you have multiple commits which need squashing

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.

Scenario 5: The parent branch has been changed via a force push since you branched off of it

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment