Matt Greensmith bio photo

Matt Greensmith

Infrastructure and systems-focused software engineer and cloud architect.

Email Twitter LinkedIn Github

GitHub’s full-featured API allows us to do really neat things, like commit to a git repository programmatically, without having to have a local git installation or a local copy of the repo.

Huge thanks to Matt Swanson for doing the digging and figuring out the (relatively) easy 5-step process for making a commit. I want to take his work one step further and do the whole commit in code. Ocktokit is the official wrapper library for the GitHub API, and there’s a ruby flavor available as a gem, so let’s make a programmatic commit to GitHub with octokit!

(Skip to the end for the full code listing)

Set up a github client instance; you can authenticate with a username and password, or an OAuth token

Update: I wrote this post against Octokit 1.x, but Octokit 2.0 is now stable, and :oauth_token has been renamed to :access_token

require 'octokit'

github = Octokit::Client.new( :access_token => "1hw9782egfbioj3fo32hf893fgb32yfv238fy" ) # Octokit 2.x
#github = Octokit::Client.new( :oauth_token => "1hw9782egfbioj3fo32hf893fgb32yfv238fy" ) # Octokit 1.x
# or
#github = Octokit::Client.new( :login => "me", :password => "sekret" )

# set up some vars:
repo = 'mgreensmith/api-test'
ref = 'heads/master'

I’ll be using the master branch, which is referenced as heads/master in git-speak. Responses from Octokit are returned as a Hashie::Mash, so we can use dotted-path notation to drill into the nested hash and get the correct attribute. We first find and store the SHA for the latest commit (the commit that heads/master points at, aka the HEAD of master branch).

sha_latest_commit = github.ref(repo, ref).object.sha

Find and store the SHA for the tree object that the heads/master commit points to.

sha_base_tree = github.commit(repo, sha_latest_commit).commit.tree.sha

Now we know the tree upon which we will base our new commit.

Let’s put some data into the commit. Start by creating a new blob object by base64-encoding a local object (my_content) and then pushing it to get a blob SHA in return. We then push a new tree object containing that blob at a particular file path and capture the SHA of this new tree. We are basing this tree upon the existing remote sha_base_tree object that we obtained in the last step (the tree which represents the object at which the HEAD commit of master is pointed).

file_name = File.join("some_dir", "new_file.txt")
blob_sha = github.create_blob(repo, Base64.encode64(my_content), "base64")
sha_new_tree = github.create_tree(repo, 
                                   [ { :path => file_name, 
                                       :mode => "100644", 
                                       :type => "blob", 
                                       :sha => blob_sha } ], 
                                   {:base_tree => sha_base_tree }).sha

Now we will generate a new commit containing the tree object that we just created. We are making a regular commit, and we will specify HEAD of master as the parent commit. If this were a merge commit, we would need to specify two parent commits. From the response, we capture the SHA of this new commit.

commit_message = "Committed via Octokit!"
sha_new_commit = github.create_commit(repo, commit_message, sha_new_tree, sha_latest_commit).sha

Finally, we move the reference heads/master to point to our new commit object.

updated_ref = github.update_ref(repo, ref, sha_new_commit)
puts updated_ref

Octokit will throw exceptions on any API failure, so if we’ve made it this far, congratulations, we’re done! Check out your fresh new commit in GitHub.

For further learning, check out the GitHub API documentation and the Octokit RubyDocs.

Here’s the full code listing:

require 'octokit'

github = Octokit::Client.new( :access_token => "1hw9782egfbioj3fo32hf893fgb32yfv238fy" ) # Octokit 2.x
#github = Octokit::Client.new( :oauth_token => "1hw9782egfbioj3fo32hf893fgb32yfv238fy" ) # Octokit 1.x
# or
#github = Octokit::Client.new(:login => "me", :password => "sekret")

# set up some vars:
repo = 'mgreensmith/api-test'
ref = 'heads/master'

sha_latest_commit = github.ref(repo, ref).object.sha
sha_base_tree = github.commit(repo, sha_latest_commit).commit.tree.sha
file_name = File.join("some_dir", "new_file.txt")
blob_sha = github.create_blob(repo, Base64.encode64(my_content), "base64")
sha_new_tree = github.create_tree(repo, 
                                   [ { :path => file_name, 
                                       :mode => "100644", 
                                       :type => "blob", 
                                       :sha => blob_sha } ], 
                                   {:base_tree => sha_base_tree }).sha
commit_message = "Committed via Octokit!"
sha_new_commit = github.create_commit(repo, commit_message, sha_new_tree, sha_latest_commit).sha
updated_ref = github.update_ref(repo, ref, sha_new_commit)
puts updated_ref