Posts Categorized “programming”

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.

Code coverage wisdom

What is the proper percentage of test coverage? This morning I discovered a great story that answers it. Enjoy!

The story

“I am ready to write some unit tests.", the programmer said, "What code coverage should I aim for?” The great master looked at him and respond. "Don’t worry about coverage, just write some good tests.” The programmer smiled, bowed, and left.

Later that day, a second programmer asked the same question. The great master pointed at a pot of boiling water and replied. “How many grains of rice should put in that pot?” The programmer, looking puzzled, replied. “How can I possibly tell you? It depends on how many people you need to feed, how hungry they are, what other food you are serving, how much rice you have available, and so on.” "Exactly", the great master replied. The second programmer smiled, bowed, and left.

Toward the end of the day, a third programmer came and asked the same question about code coverage. The great master replied in a stern voice, pounding his fist on the table. “Eighty percent and no less!” The third programmer smiled, bowed, and left.

After this last reply, a young apprentice approached the great master. “Great master, today I overheard you answer the same question about code coverage with three different answers. Why?” The great master stood up from his chair and replied. “Come get some fresh tea with me and let’s talk about it.”

After they filled their cups with smoking hot green tea, the great master began to answer. “The first programmer is new and just getting started with testing. Right now he has a lot of code and no tests. He has a long way to go; focusing on code coverage at this time would be depressing and quite useless. He’s better off just getting used to writing and running some tests. He can worry about coverage later.”

“The second programmer, on the other hand, is quite experience both at programming and testing. When I replied by asking her how many grains of rice I should put in a pot, I helped her realize that the amount of testing necessary depends on a number of factors, and she knows those factors better than I do – it’s her code after all. There is no single, simple, answer, and she’s smart enough to handle the truth and work with that.”

“I see,” said the young apprentice, “but if there is no single simple answer, then why did you answer the third programmer ‘Eighty percent and no less’?”

The great master laughed so hard and loud that his belly, evidence that he drank more than just green tea, flopped up and down.

“The third programmer wants only simple answers – even when there are no simple answers, and then does not follow them anyway.” The young apprentice and the grizzled great master finished drinking their tea in contemplative silence.