GitHub Workflow
Introduction
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:
- Open a GitHub PR (Pull Request) by creating a new feature branch from
master
- Make feature specific changes and request a code review
- If given a “thumbs up”, this means the PR author is allowed to handle merging the PR
- The merge process requires a set of sub steps (see below)
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:
- Interactively rebase
master
onto the feature branch - Squash all feature commits into a single commit:
- we’ll see an example of this later (as this isn’t exactly a
squash
) - the first line of the commit message should be the same as the GitHub PR title
- we’ll see an example of this later (as this isn’t exactly a
- As part of the rebase ensure:
- the author(s) is mentioned in the commit message
- the PR is mentioned (and any associated issues as well)
- Move back to the
master
branch andcherry-pick
in the newly squashed feature commit
Example
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 usereword
andfixup
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 @@
+foo
+bar
+baz
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
But before we wrap up... time (once again) for some self-promotion 🙊