This is a tutorial on how to programatically create single commit for multiple file changes, which is not obvious from Octokit API.
GitHub has a low-level Git Data API. You can do basically everything with Git
via this powerful API!
In this tutorial, I am going to walk you through how to use this API with Octokit to change files in one single commit in a new branch and send a Pull Request.
Suppose we want to send a Pull Request for https://github.com/JuanitoFatas/git-playground with these changes:
- append
bar
to file foo - append
baz
to file bar - in one commit with the message "Update foo & bar in a new topic branch "update-foo-and-bar".
This is how you could do it:
$ gem install octokit
Get an access token, and open irb with octokit required, then create an Octokit client with your token:
$ irb -r octokit
> client = Octokit::Client.new(access_token: "<your 40 char token>")
We also prepare two variables to be used later, the repo name and new branch name:
repo = "JuanitoFatas/git-playground"
new_branch_name = "update-foo-and-bar"
First, let's get the base branch (in this case, master branch) SHA1, so that we can branch from master.
We can use the Octokit#refs
method to get the base branch SHA1:
master = client.refs(repo).find do |reference|
"refs/heads/master" == reference.ref
end
base_branch_sha = master.object.sha
And creates a new branch from base branch via Octokit#create_ref
method:
new_branch = client.create_ref(repo, "heads/#{new_branch_name}", base_branch_sha)
The tricky part here is that you need to prefix your new branch name with "heads/"
.
First let's use Octokit#contents
method with the SHA1 to get existing foo
and bar
files' content.
foo = client.contents repo, path: "foo", sha: base_branch_sha
bar = client.contents repo, path: "foo", sha: base_branch_sha
Contents on GitHub API is Base64-encoded, we need to decode and append "bar" to foo
file, "baz" to bar
file respectively:
require "base64"
# path => new content
new_contents = {
"foo" => Base64.decode64(foo.content) + "bar",
"bar" => Base64.decode64(foo.content) + "baz"
}
Creates a new tree with our new files (blobs), the new blob can be created via (Octokit#create_blob
method). This new tree will be part of our new “tree”.
new_tree = new_contents.map do |path, new_content|
Hash(
path: path,
mode: "100644",
type: "blob",
sha: client.create_blob(repo, new_content)
)
end
Get the current commit first via Octokit#git_commit
method:
commit = client.git_commit(repo, new_branch["object"]["sha"])
Note that this method is not the same as Octokit#commit
method. git_commit
is from the low-level Git Data API, while commit
is using the Commits API.
Now we get the commit object, we can retrieve the tree:
tree = commit["tree"]
Creates a new tree by Octokit#create_tree
method with the blobs object we created earlier:
new_tree = client.create_tree(repo, new_tree, base_tree: tree["sha"])
The base_tree
argument here is important. Pass in this option to update an existing tree with new data.
Now our new tree is ready, we can add a commit onto it:
commit_message = "Update foo & bar"
new_commit = client.create_commit(repo, commit_message, new_tree["sha"], commit["sha"])
Finally, update the reference via Octokit#update_ref
method on the new branch:
client.update_ref(repo, "heads/#{new_branch_name}", new_commit["sha"])
Creates a new Pull Request via Octokit#create_pull_request
method:
title = "Update foo and bar"
body = "This Pull Request appends foo with `bar`, bar with `baz`."
client.create_pull_request(repo, "master", new_branch_name, title, body)
That's it! :sparkles: See the result here.
Now you can do basically everything with Git via GitHub's Git Data API!
May the Git Data API be with you.
Thanks for reading!