Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Script to upload files to github: ./github-upload <file> [<repo>]
#!/usr/bin/env ruby
# under Ubuntu, you should install ruby1.9.1 or higher (default 1.8 does not work)
require 'json'
require 'net/https'
require 'pathname'
# Extensions
# ----------
# We extend Pathname a bit to get the content type.
class Pathname
def type
flags = RUBY_PLATFORM =~ /darwin/ ? 'Ib' : 'ib'
`file -#{flags} #{realpath}`.chomp.gsub(/;.*/,'')
# Helpers
# -------
# Die if something goes wrong.
def die(msg); puts(msg); exit!(1); end
# Do a post to the given url, with the payload and optional basic auth.
def post(url, token, params, headers)
uri = URI.parse(url)
http =, uri.port)
http.use_ssl = true
req =, headers)
req['Authorization'] = "token #{token}" if token
return http.request(req, params)
def urlencode(str)
str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0].to_i) }
# Yep, ruby net/http doesn't support multipart. Write our own multipart generator.
# The order of the params is important, the file needs to go as last!
def build_multipart_content(params)
parts, boundary = [], "#{rand(1000000)}-we-are-all-doomed-#{rand(1000000)}"
params.each do |name, value|
data = []
if value.is_a?(Pathname) then
data << "Content-Disposition: form-data; name=\"#{urlencode(name.to_s)}\"; filename=\"#{value.basename}\""
data << "Content-Type: #{value.type}"
data << "Content-Length: #{value.size}"
data << "Content-Transfer-Encoding: binary"
data << ""
data <<
data << "Content-Disposition: form-data; name=\"#{urlencode(name.to_s)}\""
data << ""
data << value
parts << data.join("\r\n") + "\r\n"
[ "--#{boundary}\r\n" + parts.join("--#{boundary}\r\n") + "--#{boundary}--", {
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
# Configuration and setup
# -----------------------
# Get Oauth token for this script.
token = `git config --get github.upload-script-token`.chomp
# The file we want to upload, and repo where to upload it to.
file =[0])
repo = ARGV[1] || `git config --get remote.origin.url`.match(/\.git/)[1]
# The actual, hard work
# ---------------------
# Register the download at github.
res = post("{repo}/downloads", token, {
'name' => file.basename.to_s, 'size' => file.size.to_s,
'content_type' => file.type.gsub(/;.*/, '')
}.to_json, {})
die("File already exists.") if res.class == Net::HTTPClientError
die("GitHub doens't want us to upload the file.") unless res.class == Net::HTTPCreated
# Parse the body and use the info to upload the file to S3.
info = JSON.parse(res.body)
res = post(info['s3_url'], nil, *build_multipart_content({
'key' => info['path'], 'acl' => info['acl'], 'success_action_status' => 201,
'Filename' => info['name'], 'AWSAccessKeyId' => info['accesskeyid'],
'policy' => info['policy'], 'Signature' => info['signature'],
'Content-Type' => info['mime_type'], 'file' => file
die("S3 is mean to us.") unless res.class == Net::HTTPCreated
# Print the URL to the file to stdout.
puts "#{info['s3_url']}#{info['path']}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.