Split multiple commits in git
I have added a new feature to the node auth module to not follow redirects. Next to functional changes I also removed some trailing white spaces. This to satisfy tools like jshint.
Ciaran, the maintainer of the module, tell me that he likes what I did but would like to see the functional- and white spaces changes in separate commits. This means I need to split some commits in made. Splitting commits and rewriting git repository history is not something I do on a daily basis, and I guess there are more out there, so I decided to write down how I did it for future reference.
The commits
Here is the list of commits I made:
3458576 Adds test to proof default value for followRedirects is true
e16527e Add followRedirect client option to turn auto follow on or off
04eb6fa Add test cases for 302 response status
6e215f9 Add failing test for 301 redirect for followRedirect client option
Only the last two, 3458576
and e16527
, contain whitespace fixes. I want to split the work I did in both commits into two new commits. One containing the functional change and the other the whitespace change.
Creating a backup
Before I start messing around I create a new branch backup
. In my case this isn't really necessary because the changes are already pushed to Github. But I'm not sure all the readers of this have.
git branch backup
Rebase commits
I use git rebase to rebase my two commits onto the HEAD. I specify -i
to do this in interactive mode. Which gives me the opportunity to stop after each commit and split the functional and whitespace work in multiple commits.
git rebase -i 04eb6fa
pick e16527e Add followRedirect client option to turn auto follow on or off
pick 3458576 Adds test to proof default value for followRedirects is true
# Rebase 04eb6fa..3458576 onto 04eb6fa
#
# 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 want to mark them with edit
I change the status for both commits from pick
to edit
.
edit e16527e Add followRedirect client option to turn auto follow on or off
edit 3458576 Adds test to proof default value for followRedirects is true
Now I write the file and quit my editor VIM to start the rebase.
Stopped at first commit
Git response with the following message:
stopped at e16527e... Add followRedirect client option to turn auto follow on or off
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
When I execute git log --pretty=format:'%h %s' -n 3
I get the following:
e16527e Add followRedirect client option to turn auto follow on or off
04eb6fa Add test cases for 302 response status
6e215f9 Add failing test for 301 redirect for followRedirect client option
This shows that the HEAD and index are both at e16527
. I now mixed reset that commit.
git reset HEAD^ --mixed
Unstaged changes after reset:
M lib/oauth.js
Stage hunks
The HEAD and index are now both at 04eb6fa
but my working directory contains the work I have done in the commit that I want to split. I will now stage what I want to commit first. In my case there is only a single file that has been changed, lib/oauth.js
. I specify to stage only parts of the file with the -p
option.
git add -p lib/oauth.js
Git will now ask me for every part what to do. This is what the first part looks like:
diff --git a/lib/oauth.js b/lib/oauth.js
index 7607ee6..2998ab4 100644
--- a/lib/oauth.js
+++ b/lib/oauth.js
@@ -29,7 +29,8 @@ exports.OAuth= function(requestUrl, accessUrl, consumerKey, consumerSecret, vers
"Connection" : "close",
"User-Agent" : "Node authentication"}
this._clientOptions= this._defaultClientOptions= {"requestTokenHttpMethod": "POST",
- "accessTokenHttpMethod": "POST"};
+ "accessTokenHttpMethod": "POST",
+ "followRedirects": true};
this._oauthParameterSeperator = ",";
};
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?
Staging a hunk has the following options:
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk nor any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk nor any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help
I choose y
to stage this hunk. The next hunk appears, it is also a functional change. I choose y
again and the next hunk appears. I continue this process by choosing y
to stage and n
to not stage the hunks until there is no hunk left.
tip: Enabling git colours will help you to understand the diff output better. To enable git colours globally run: git config --global color.ui auto
.
Commit it
Now parts of the lib/oauth.js
are staged. I simply commit this with:
git commit -m 'Add followRedirect client option to turn auto follow on or off'
Add the whitespace changes
All functional changes are staged and committed. Now I can simply stage the full lib/oauth.js
file and commit that as well.
git add lib/oauth.js
git commit -m 'Remove trailing white spaces'
Continue the rebase
I have split commit e16527e
in two new commits. Now I continue the rebase which will move us to the next commit 3458576
.
git rebase --continue
Stopped at 3458576... Adds test to proof default value for followRedirects is true
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
Once again I reset the HEAD and the index without resetting the actual content of the files.
git reset HEAD^ --mixed
Unstaged changes after reset:
M tests/oauth.js
I stage tests/oauth.js
with the -p
option:
git add tests/oauth.js -p
After accepting the first hunk I discover that there are no hunks left. Which means I did not make any whitespace changes to this file.
diff --git a/tests/oauth.js b/tests/oauth.js
index ab1c296..99e5bb6 100644
--- a/tests/oauth.js
+++ b/tests/oauth.js
@@ -24,6 +24,12 @@ DummyRequest.prototype.end= function(){
}
vows.describe('OAuth').addBatch({
+ 'When newing OAuth': {
+ topic: new OAuth(null, null, null, null, null, null, "PLAINTEXT"),
+ 'followRedirects is enabled by default': function (oa) {
+ assert.equal(oa._clientOptions.followRedirects, true)
+ }
+ },
'When generating the signature base string described in http://oauth.net/core/1.0/#sig_base_example': {
topic: new OAuth(null, null, null, null, null, null, "HMAC-SHA1"),
'we get the expected result string': function (oa) {
Stage this hunk [y,n,q,a,d,/,e,?]? y # this is a functional change
I commit with a slightly different message than the original. I start with add instead of adds to make my commit message consistent":
git commit -m 'Add test to proof default value for followRedirects is true'
Finish rebase
I tell git to continue with the rebase.
git rebase --continue
The rebase will finish because there are not commits left.
Successfully rebased and updated refs/heads/no-follow-option.
Push with force
I now want to push my changes to Github to update my pull request.
git push origin no-follow-redirect
Git rejects my commit because my HEAD doesn't match with the HEAD of the remote.
To git@github.com:pjvds/node-oauth.git
! [rejected] no-follow-option -> no-follow-option (non-fast-forward)
error: failed to push some refs to 'git@github.com:pjvds/node-oauth.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
This may look scary and definitely is when others are working on my branch as well. But in this case I want to push anyway and discard the wrong history of the remote. I now push with the -f
force option.
git push origin no-follow-redirect -f
And I am done!
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (12/12), 1.43 KiB, done.
Total 12 (delta 8), reused 0 (delta 0)
To git@github.com:pjvds/node-oauth.git
+ 3458576...3584b43 no-follow-option -> no-follow-option (forced update)
Final pull request
Here is the final pull request version that - hopefully - satisfies Carian: Don't follow redirects opt-out.