Blog icon

Using git rebase and git merge to optimise your pull requests

By Daniel Heath,
Daniel Heath
Scroll down to read

A popular and effective way of developing a complex application in a team setting is to make use of git feature branches and submitting a pull request to be merged into the develop branch. Sometimes, though, the pull request can become quite large. This may result in a lengthy wait to get your pull request reviewed, bugs being overlooked, and merge conflicts.

These difficulties can be helped by shrinking your pull requests using the following two strategies:

  • get your work merged as early and as often as possible
  • extract small changes into their own pull request that can be reviewed/merged before the feature is fully complete

Shrinking PRs by moving commits aside

image-1

Let's say you have some change here that doesn't need to be in your feature. It can be merged now. You can shift it out to it's own branch so it can be merged now.

Moving a commit aside safely

Here is one way (of many) that you can do it:

# go to your working branch
git checkout feature/my-feature-branch

# create a new branch with the same changes as the working branch above
git checkout -b fixup/my-small-change

# start an interactive rebase to remove the feature, keeping only the small change
git rebase -i origin/develop

# push your changes
git push -u origin fixup-my-small-change

This is just one of the ways you can do this. You can also use git reset or git cherry-pick.

If your changes are interspersed with your code, and there isn't just one commit that you can easily pull out, you can extract them to a branch as follows:

# create a new branch from your feature branch
git checkout -b fixup/my-small-change

# keep your changes, but not your commits (this unstages all your changes)
git reset origin/develop

# use either gitx or `git add -p` to add and commit just the changes you want

# push your changes
git push -u origin fixup/my-small-change

These two examples make use of git merge and git rebase, which I will explain in more detail below.

Git - merge

In this example, we created a feature branch and added some commits. In the meantime, three commits have been added to develop since we created our branch:

image-2

When you do a git merge, that creates a new merge commit. This merge commit records the fact that history separated and then came back together. You then update develop to point to that new merge commit. That essentially adds all of the changes from your feature branch into the develop branch.

image-3

You must resolve any conflicts when you create the merge commit. If there are any merge conflicts, the details of how to resolve those conflicts are included in the merge commit. If there aren't any conflicts, the merge commit simply records the fact that these two branches were brought together.

Git - rebase and merge

Again, in this example, several commits have been added to develop since we created our branch:

image-4

Rebasing your branch from develop applies your changes onto the end of the changes on the develop branch:

image-5

Now your feature has all of the stuff in the develop branch followed by all the changes you made in your feature branch.

A rebase adds the commits one by one and, if there are any merge conflicts, you need to resolve them then and there. In some cases you might need to resolve conflicts in the same file several times as you progress through all the commits.

At the end, when you merge your feature branch into the develop branch, it will generate a merge commit but there won't be any merge conflicts to deal with.

What is a rebase?

rebase-i

git rebase -i develop means:

  • open editor with a file that lists all the commits that exist between develop and your branch
  • you can edit that file and save it
  • closing that file will cause your environment to reset back to develop
  • for each line in the file, it will cherry-pick that commit and execute them one after another

If you change the order of the commits in the file, it will change their order in history.

When not to rebase

Because git rebase rewrites history, do not rebase if someone else has pulled your branch down, unless they specifically ask you to (and they know what they are doing). A rebase will force them to fix up their copy of the repo which can be fraught with danger.

Escape hatch when everything goes wrong

Escape hatch for rebase

If you get stuck during a rebase, the following command will get you out of trouble and allow you to start again:

git rebase --abort

If you rebase and you lose one of your changes, you can find it again using git reflog. It will list all the commits that you have been at recently:

image-6

You can then run a git reset to restore that commit. Using the example above, to restore commit 94d2f1dd, run git reset 94d2f1dd.

FYI, Git doesn't fully delete anything until it is older than 30 days and hasn't been referenced.

Escape hatch for everything else

This should sort out any repo that is stuck:

# Gather up all your changes and save them to the stash
git add . && git stash save

# Cancel any in-progress operations
git cherry-pick --abort
git merge --abort
git rebase --abort
git checkout develop

Note: Instead of git add . && git stash save, you can use git stash --all which will take all the staged files as well as any files that you haven't added to the repo before and stash them.

Internals

I found git much less intimidating once I found out how it stores files and commits The git book is well-written, free, and not a very long read. It's a great way to spend half an hour and well worth reading.

To find out how reinteractive can turn your web application vision into reality, get in touch with us through our contact form or call us on +61 2 8019 7252.