Created
June 15, 2012 07:27
-
-
Save harlantwood/2935203 to your computer and use it in GitHub Desktop.
Commit and push via Github REST API, from ruby RestClient
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Committing changes to a repo via the Github API is not entirely trivial. | |
# The five-step process is outlined here: | |
# http://developer.github.com/v3/git/ | |
# | |
# Matt Swanson wrote a blog post translating the above steps into actual API calls: | |
# http://swanson.github.com/blog/2011/07/23/digging-around-the-github-api-take-2.html | |
# | |
# I was not able to find sample code for actually doing this in Ruby, | |
# either via the HTTP API or any of the gems that wrap the API. | |
# So in the hopes it will help others, here is a simple function to | |
# commit a single file to github via the API. The code only handles | |
# the case of committing a single file to the master branch, | |
# but should be easy to modify for your own needs. | |
# | |
# Note also that we use HTTP basic auth, not OAuth, in this example. | |
# | |
# Prerequisites: | |
# | |
# $ gem install rest-client | |
# $ export GH_USERNAME=xxxx | |
# $ export GH_PASSWORD=yyyy | |
# | |
# Usage: | |
# | |
# push_to_github :path => "path/to/file.txt", :content => "hello commit", :repo => 'test' | |
# | |
# In the above example, the repo 'test' must be owned by GH_USERNAME, and have at least one commit. | |
# | |
# No news is good news. If you don't raise exceptions, you should see the commit in your repo. | |
# | |
# CC0 Public Domain Dedication | |
# To the extent possible under law, Harlan T Wood | |
# has waived all copyright and related or neighboring | |
# rights to this work. | |
# http://creativecommons.org/publicdomain/zero/1.0/ | |
# | |
require 'rest_client' | |
require 'json' | |
def push_to_github(params) | |
repo = params[:repo] | |
# get the head of the master branch | |
# see http://developer.github.com/v3/git/refs/ | |
branch = github(:get, repo, "refs/heads/master") | |
last_commit_sha = branch['object']['sha'] | |
# get the last commit | |
# see http://developer.github.com/v3/git/commits/ | |
last_commit = github :get, repo, "commits/#{last_commit_sha}" | |
last_tree_sha = last_commit['tree']['sha'] | |
# create tree object (also implicitly creates a blob based on content) | |
# see http://developer.github.com/v3/git/trees/ | |
new_content_tree = github :post, repo, :trees, | |
:base_tree => last_tree_sha, | |
:tree => [{:path => params[:path], :content => params[:content], :mode => '100644'}] | |
new_content_tree_sha = new_content_tree['sha'] | |
# create commit | |
# see http://developer.github.com/v3/git/commits/ | |
new_commit = github :post, repo, :commits, | |
:parents => [last_commit_sha], | |
:tree => new_content_tree_sha, | |
:message => 'commit via api' | |
new_commit_sha = new_commit['sha'] | |
# update branch to point to new commit | |
# see http://developer.github.com/v3/git/refs/ | |
github :patch, repo, "refs/heads/master", | |
:sha => new_commit_sha | |
end | |
def github(method, repo, resource, params={}) | |
resource_url = "https://#{ENV['GITHUB_USER']}:#{ENV['GITHUB_PASS']}@api.github.com" + | |
"/repos/#{ENV['GITHUB_USER']}/#{repo}/git/#{resource}" | |
if params.empty? | |
JSON.parse RestClient.send(method, resource_url) | |
else | |
JSON.parse RestClient.send(method, resource_url, params.to_json, :content_type => :json, :accept => :json) | |
end | |
end |
@dornan2 did you ever make it?
Not sure how correct this is, as it's the only Go code I have ever written but worked for me at the time. 👍
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
b64 "encoding/base64"
"os"
)
type GenericSha struct {
Sha string `json:"sha"`
}
type FileContents struct {
Content string `json:content`
}
type LastCommit struct {
Object struct {
Sha string `json:"sha"`
} `json:"object"`
}
func parseLastCommit(body *http.Response) string {
response := LastCommit{}
resBody, _ := ioutil.ReadAll(body.Body)
if err := json.Unmarshal(resBody, &response); err != nil {
panic(err)
}
return response.Object.Sha
}
func parseSha(body *http.Response)string{
response := GenericSha{}
resBody, _ := ioutil.ReadAll(body.Body)
if err := json.Unmarshal(resBody, &response); err != nil {
panic(err)
}
return response.Sha
}
func parseContent(body *http.Response) []byte{
response := FileContents{}
resBody, _ := ioutil.ReadAll(body.Body)
if err := json.Unmarshal(resBody, &response); err != nil {
panic(err)
}
decodedBody, _ := b64.StdEncoding.DecodeString(response.Content)
return decodedBody
}
func buildRequest(method, url string, body io.Reader) *http.Response {
client := &http.Client{}
req, _ := http.NewRequest(method, url, body)
req.Header.Set("Authorization", "token xxxxxx")
return res
}
//func easyPrint(body *http.Response) {
//
// bodyBytes, _ := ioutil.ReadAll(body.Body)
// fmt.Println(string(bodyBytes))
//}
//func getFileContents(repoName, filename string) []byte{
//
// base := "https://api.github.com"
// owner := "[OWNER]"
//
// // Get the contents of a file
// // GET /repos/:owner/:repo/contents/:path
// // https://developer.github.com/v3/repos/contents
//
// getFileBody := []byte(`
// {
// "path": "project/` +filename+`",
// "ref": "master"
// }`)
//
// getFileResponse := buildRequest("GET", base + "/repos/"+owner+"/"+repoName+"/contents/project/" +filename, bytes.NewBuffer(getFileBody))
//
//
// return parseContent(getFileResponse)
//}
func getFileContents(repoName, filename string) *http.Response {
client := &http.Client{}
//Build url to look up
url := "https://raw.githubusercontent.com/[OWNER]/" + repoName + "/master/project/" + filename
//Check if file exists
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "token XXXXXXXX")
res, err := client.Do(req)
//Check if errors
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
return nil
}
return res
}
func pushChanges(branchName, filename, content, repoName string) {
base := "https://api.github.com"
owner := "OWNER"
// 1. get the head of the master branch
// see http://developer.github.com/v3/git/refs/
// GET repos/:owner/:repo/git/refs/:ref
lastCommit := buildRequest("GET", base + "/repos/"+owner+"/"+repoName+"/git/refs/heads/master", nil)
lastCommitSHA := parseLastCommit(lastCommit)
//========================================================================================================================
// 2. Create a new branch from last commit
// see https://developer.github.com/v3/git/refs/
// POST repos/:owner/:repo/git/refs/
newBranchBody := []byte(`
{
"ref": "refs/heads/` + branchName + `",
"sha": "` + lastCommitSHA + `"
}`)
buildRequest("POST", base + "/repos/"+owner+"/"+repoName+"/git/refs", bytes.NewBuffer(newBranchBody))
//========================================================================================================================
// 3. Get sha of file being replaced
// https://developer.github.com/v3/repos/contents
// GET /repos/:owner/:repo/contents/:path
getFileBody := []byte(`
{
"path": "project/` +filename+`",
"ref": "`+ branchName + `"
}`)
getFileResponse := buildRequest("GET", base + "/repos/"+owner+"/"+repoName+"/contents/project/" +filename, bytes.NewBuffer(getFileBody))
oldFileSHA := parseSha(getFileResponse)
//========================================================================================================================
// 4. Update a file
// https://developer.github.com/v3/repos/contents/
// PUT /repos/:owner/:repo/contents/:path
updateBody := []byte(`
{
"message": "updating ` + branchName + `",
"content": "` + content +`",
"branch": "` + branchName + `",
"sha": "` + oldFileSHA + `"
}`)
buildRequest("PUT", base + "/repos/"+owner+"/"+repoName+"/contents/project/" +filename, bytes.NewBuffer(updateBody))
//========================================================================================================================
// 5. Open a pull request
// see https://developer.github.com/v3/pulls
// POST /repos/:owner/:repo/pulls
openPRBody := []byte(`
{
"title": "` + branchName + `",
"body": "Please pull this in!",
"head": "` + branchName + `",
"base": "master"
}`)
buildRequest("POST", base + "/repos/"+owner+"/"+repoName+"/pulls", bytes.NewBuffer(openPRBody))
}
I made a slightly more complex version of this in Typescript: https://github.com/ubccsss/content-manager/blob/5ff50b9b17a7bb124034e2b0147671572a101d96/src/api/github.ts#L110
It allows you to add multiple image files in the commit.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This has been so helpful thank you. I will post up my Go translation later!