GitHub Workflow (used by Frameworks team at BBC News)


This is a quick post to cover a GitHub workflow that is utilised by our specific team (Frameworks) here at BBC News. The basis of our workflow is this:

Rebase before merge

At this point the PR author has been given a "thumbs up" and is preparing their PR to be merged back into the master branch. The steps (in summary) are:


Below is an example set of commits we'll be working from. I create a master branch and then branch off from that with a new feature branch:

git init
touch test.txt
git commit -am "Initial file"

[master (root-commit) 85919e1] Initial file
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test.txt

git checkout -b feature
Switched to a new branch 'feature'

echo foo >> test.txt
git commit -am "Foo"

echo bar >> test.txt
git commit -am "Bar"

echo baz >> test.txt
git commit -am "Baz"

# Check commits we now have in this feature branch
# Note: this is a custom shell alias
git lg

* 62d4c80 - Baz (HEAD, feature)
* a5827db - Bar
* ae1a4a5 - Foo

At this point, let's imagine our feature PR has been approved to be merged:

# Make sure master is up to date
git checkout master
git pull --rebase origin master

# Carry out the interactive rebase
git checkout feature
git rebase -i master

Now at this point you should see something like the following in your terminal:

pick ae1a4a5 Foo
pick a5827db Bar
pick 62d4c80 Baz

# Rebase 85919e1..62d4c80 onto 85919e1
# 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
# 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

We're now ready to modify our git history. So let's squash all our commits down into a single commit:

reword ae1a4a5 Foo
fixup a5827db Bar
fixup 62d4c80 Baz

Note: we don't use squash as that automatically uses the existing commit message from the commit we're squashing other commits into (but our requirements mean we wish to modify that commit message); so we use reword and fixup instead

Let's apply these changes by executing :wq. Once you do this, Git will carry out the rebase and then drop you back to the COMMIT_EDITMSG screen. You can now modify the commit message so it is the same as the title of your GitHub PR (and you can inform GitHub of what PR to automatically close when this commit arrives in master by using the keyword: closes; you'll notice there is the keyword fixes which indicates a GitHub issue to close).

New Feature X

Closes #1 and Fixes #11
Authors @integralist @stevenjack
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Date:      Sat Dec 27 16:19:54 2014 +0000
# rebase in progress; onto 85919e1
# You are currently editing a commit while rebasing branch 'feature' on '85919e1'.
# Changes to be committed:
#   modified:   test.txt

We can now see (when executing git log) that the three individual feature commits are now a single commit with a commit hash of 68f5bee. We can move back to master (i.e. git checkout master) and then cherry-pick the squashed commit into it:

git checkout master
git cherry-pick 68f5bee
git branch -D feature
git push origin master

(Bonus) Modifying content within an interactive rebase

One aspect of carrying out an interactive rebase that seems to confuse a lot of users is the ability to edit the content of a specific commit.

I think the reason being is that when you select a commit to edit, the interactive rebase drops you at that specific commit, so you'll find there aren't any files in the staging area.

To make edits at that point you need to undo the commit, so the files end up back in the staging area, ready to be modified and a new commit made.

Let's take a look at an example, using our earlier example:

git lg

* 76c99a2 - New Feature X (HEAD, master)
* 85919e1 - Initial file

Let's say we want to make an edit to the commit 76c99a2. To do that we'll need to start up an interactive rebase like so (we specify the commit before the one we want to edit):

git rebase -i 85919e1

Stopped at 76c99a27e64b5f749ac8e3d3c7032f53954c760a... New Feature X
You can amend the commit now, with

        git commit --amend 

Once you are satisfied with your changes, run

        git rebase --continue

At this point we want to execute the following command, which will undo the commit (but keeps the changes from that commit) and place the files back into the staging area:

git reset --mixed 85919e1

Unstaged changes after reset:
M       test.txt

Now if you check the diff on the files you'll see the changes from that commit have been made and are waiting to be committed again:

git diff

diff --git a/test.txt b/test.txt
index e69de29..86e041d 100644
--- a/test.txt
+++ b/test.txt
@@ -0,0 +1,3 @@

So you can now modify the test.txt file as necessary and create a new commit. When you create the new commit you finish the rebase by using the --continue feature (as seen in the above output when initially starting the rebase):

echo qux >> test.txt
git commit -am "New Feature X"
git rebase --continue

Note: if you forget where you are then running git status should give you the information you need to help you either continue rebasing or to abort the rebase