Thanks!

You are now subscribed to our monthly blog digest. Happy reading!

Click anywhere to dismiss ...

Behind the Scenes at Jungle Disk - Git Rebasing and Clean Pull Requests

Today, I would like to share some best practices for using Git rebasing and pull requests.

Development vs Pull Request Commits

When developing, the mantra of commit early and commit often may lead to an explosion at the patch factory. Prior to sending those patches upstream or opening a pull request (PR), the working branch should be cleaned up such that:

  1. Every patch is atomic
  2. Every commit message is properly formatted
  3. No extraneous commits (like merge commits)
  4. Based off as close to upstream HEAD as possible

Every Patch is Atomic

Each patch in a repository should be able to stand on its own. That is each patch should not depend on the patches after it to produce a workable copy. This will allow the use of tools like git bisect to track down where a bug was introduced. If there are a set of patches A, B, C where patch B requires patch C in order to function properly, B and C should be merged into a single patch.

Every Commit Message is Properly Formatted

Every commit message should be formatted in such a way to make it easy to read in a variety of situations. For example a commit message that is one long line makes it very hard to read via git log --oneline --graph. Generally following the guidelines of the kernel and git itself leads to very readable and usable commit messages:

area: short summary

Long summary that describes the change in detail. This area should wrap
at 72 characters in order to allow space for the standard indention
from `git log` such that it doesn't wrap over 80 characters.

Fixes: #53

Area should be the general area that or realm of the project that the patch modifies. The area and short summary are separated by a : and the first line of the patch should not exceed 50 characters. (This allows plenty of space for --oneline formatting.) The rest of the commit message wraps at 72 characters. Following the long summary, any additional tags can be added such as Fixes, Signed-Off-By, et al.

No Extraneous Commits

With some exceptions merge commits should never appear in a branch that is intended to send a pull request for. Merge commits are useful when two long running branches are merged together. Such as if there is a development branch and a master branch. The merge commit is the record of the true up between the branches.


     A---B---C development
    /
D---E---F---G master


      A---B---C development
	 /         \
 D---E---F---G---H master

source: GIT-MERGE(1) man page

Generally when working on a topic branch, the branch will be forgotten or removed after it is merged upstream, so there is no need to track the true up between it and upstream. Prior to submitting a pull request upstream, any merge commits introduced during development should be removed. By following a rebase procedure instead of merge, merge commits can avoid them altogether.

Based Off as Close to Upstream HEAD as Possible

Over time the branch that will receive the pull request to and the working topic branch will diverge. Prior to submitting the pull request, all conflicts should be resolved with the current upstream HEAD.

Rebasing to Squash and Fix Up

The easiest way to clean up a branch and get it ready for submission is a rebase (usually interactive). For this example assume that there is a remote origin with a branch master that we will submit out pull request to:

$ git fetch origin
$ git rebase -i origin/master

git will open $EDITOR with the file .git/rebase-merge/git-rebase-todo. This file controls the rebase operation and allows fixing up the branch. Each commit listed will be cherry-picked on top of the HEAD specified and the current branch HEAD will be rewritten to point to resulting last commit.

pick ddd461a a: commit a
pick 2107986 b: commit b
pick 1165f85 c: commit c

# Rebase ec9457b..1165f85 onto ec9457b (3 command(s))
#
# Commands:
+ p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

In the above rebase we see that there are three commits that are staged to be cherry-picked. If commit b requires c to be an atomic patch, then c should be squashed into b.


pick ddd461a a: commit a
pick 2107986 b: commit b
s 1165f85 c: commit c

Once the file is saved and exited the rebase operation will begin. Since the instruction to squash c into b might require updating the commit message, the rebase will halt temporarily and open $EDITOR with the contents of both commit messages to allow for message merging by the user.


# This is a combination of 2 commits.
# The first commit's message is:

b: commit b

# This is the 2nd commit message:

c: commit c

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Thu Nov 17 19:37:13 2016 +0000
#
# interactive rebase in progress; onto ec9457b
# Last commands done (3 commands done):
#    pick 2107986 b: commit b
#    s 1165f85 c: commit c
# No commands remaining.
# You are currently editing a commit while rebasing branch 'topic' on 'ec9457b'.
#
# Changes to be committed:
#       new file:   three
#

Once the message is satisfactory factory, saving and quitting the editor will continue the rebase operation.


# This is a combination of 2 commits.
# The first commit's message is:

b: commit b and commit c

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Thu Nov 17 19:37:13 2016 +0000
#
# interactive rebase in progress; onto ec9457b
# Last commands done (3 commands done):
#    pick 2107986 b: commit b
#    s 1165f85 c: commit c
# No commands remaining.
# You are currently editing a commit while rebasing branch 'topic' on 'ec9457b'.
#
# Changes to be committed:
#       new file:   three
#

If there are conflicts during a cherry-pick operation, rebase will halt allowing the user to fix the commit as needed before continuing:

error: could not apply 46f66fc... a: commit a

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply 46f66fcb277282c84ed3e606bbb06140d8607e80... a: commit a

To begin resolving the conflict, first run git status to see what the issue is:

$ git status
interactive rebase in progress; onto 01562b4
Last command done (1 command done):
   pick 46f66fc a: commit a
Next command to do (1 remaining command):
   pick d7b7fb1 b': commit b and commit c
  (use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'topic' on '01562b4'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   two

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add <file>..." to mark resolution)

        both modified:   one

Here both master and our topic branch modified the same line in the file one. Open the file and search for <<<<:

one
 <<<<<<< HEAD
two
=======
lulz
 >>>>>>> 46f66fc... a: commit a

This indicated that HEAD (master) edited the second line and contains the value two. The commit from the topic branch also edited the second line and contains the value lulz. From here the file can be edited to produce the correct result:

one
two
lulz

Once edited, the operation can continue with git rebase --continue. At any time, the rebase operation can be aborted by the --abort flag:

# bail bail bail
$ git rebase --abort

Until rebase becomes second nature, it is recommended that rebase operations occur on a copy of the topic branch such that it is easy to refer to the topic branch or roll back if a rebase goes wrong.

Protect Your Business Data

We are passionate about helping our customers protect their data. We want you to use Jungle Disk to protect yours. Click on Sign Up to get started. It takes less than 5 minutes!

Sign Up