Skip to content

Instantly share code, notes, and snippets.

@harlantwood
Created June 15, 2012 07:27
Show Gist options
  • Save harlantwood/2935203 to your computer and use it in GitHub Desktop.
Save harlantwood/2935203 to your computer and use it in GitHub Desktop.
Commit and push via Github REST API, from ruby RestClient
# 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
@lv7777
Copy link

lv7777 commented Jan 10, 2016

I was helped this script.
thanks!

@dornan2
Copy link

dornan2 commented Oct 3, 2018

This has been so helpful thank you. I will post up my Go translation later!

@harsh183
Copy link

@dornan2 did you ever make it?

@dornan2
Copy link

dornan2 commented May 16, 2019

@harsh183

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))

}


@AnimeAllstar
Copy link

AnimeAllstar commented Sep 14, 2022

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