From 68fafcd11e3cb3cddba6c57280bbccb3f333d996 Mon Sep 17 00:00:00 2001 From: benweet Date: Sun, 26 Oct 2014 19:13:05 +0000 Subject: [PATCH] Updated GitHub.js. Fixes #583 --- public/libs/github.js | 350 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 315 insertions(+), 35 deletions(-) diff --git a/public/libs/github.js b/public/libs/github.js index f9a1d726..8dc42699 100644 --- a/public/libs/github.js +++ b/public/libs/github.js @@ -1,59 +1,107 @@ -// Github.js 0.7.0 -// (c) 2012 Michael Aufreiter, Development Seed +// Github.js 0.9.0 +// (c) 2013 Michael Aufreiter, Development Seed // Github.js is freely distributable under the MIT license. // For all details and documentation: // http://substance.io/michael/github (function() { - var Github; + + // Initial Setup + // ------------- + + var XMLHttpRequest, Base64, _; + if (typeof exports !== 'undefined') { + XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; + _ = require('underscore'); + Base64 = require('./lib/base64.js'); + }else{ + _ = window._; + Base64 = window.Base64; + } + //prefer native XMLHttpRequest always + if (typeof window !== 'undefined' && typeof window.XMLHttpRequest !== 'undefined'){ + XMLHttpRequest = window.XMLHttpRequest; + } + + var API_URL = 'https://api.github.com'; - Github = window.Github = function(options) { + var Github = function(options) { // HTTP Request Abstraction // ======= - // + // // I'm not proud of this and neither should you be if you were responsible for the XMLHttpRequest spec. - function _request(method, path, data, cb, raw) { + function _request(method, path, data, cb, raw, sync) { function getURL() { - var url = API_URL + path; + var url = path.indexOf('//') >= 0 ? path : API_URL + path; return url + ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime(); } var xhr = new XMLHttpRequest(); if (!raw) {xhr.dataType = "json";} - xhr.open(method, getURL()); - xhr.onreadystatechange = function () { - if (this.readyState == 4) { - if (this.status >= 200 && this.status < 300 || this.status === 304) { - cb(null, raw ? this.responseText : this.responseText ? JSON.parse(this.responseText) : true); - } else { - cb({request: this, error: this.status}); + xhr.open(method, getURL(), !sync); + if (!sync) { + xhr.onreadystatechange = function () { + if (this.readyState == 4) { + if (this.status >= 200 && this.status < 300 || this.status === 304) { + cb(null, raw ? this.responseText : this.responseText ? JSON.parse(this.responseText) : true, this); + } else { + cb({path: path, request: this, error: this.status}); + } } } }; - xhr.setRequestHeader('Accept','application/vnd.github.raw'); - xhr.setRequestHeader('Content-Type','application/json'); - if ( - (options.auth == 'oauth' && options.token) || - (options.auth == 'basic' && options.username && options.password) - ) { - xhr.setRequestHeader('Authorization',options.auth == 'oauth' + xhr.setRequestHeader('Accept','application/vnd.github.v3.raw+json'); + xhr.setRequestHeader('Content-Type','application/json;charset=UTF-8'); + if ((options.token) || (options.username && options.password)) { + xhr.setRequestHeader('Authorization', options.token ? 'token '+ options.token : 'Basic ' + Base64.encode(options.username + ':' + options.password) ); } data ? xhr.send(JSON.stringify(data)) : xhr.send(); + if (sync) return xhr.response; } + function _requestAllPages(path, cb) { + var results = []; + (function iterate() { + _request("GET", path, null, function(err, res, xhr) { + if (err) { + return cb(err); + } + + results.push.apply(results, res); + + var links = (xhr.getResponseHeader('link') || '').split(/\s*,\s*/g), + next = _.find(links, function(link) { return /rel="next"/.test(link); }); + + if (next) { + next = (/<(.*)>/.exec(next) || [])[1]; + } + + if (!next) { + cb(err, results); + } else { + path = next; + iterate(); + } + }); + })(); + } + + + // User API // ======= Github.User = function() { this.repos = function(cb) { - _request("GET", "/user/repos?type=all&per_page=1000&sort=updated", null, function(err, res) { + // Github does not always honor the 1000 limit so we want to iterate over the data set. + _requestAllPages("/user/repos?type=all&per_page=1000&sort=updated", function(err, res) { cb(err, res); }); }; @@ -76,6 +124,15 @@ }); }; + // List authenticated user's unread notifications + // ------- + + this.notifications = function(cb) { + _request("GET", "/notifications", null, function(err, res) { + cb(err,res); + }); + }; + // Show user information // ------- @@ -91,7 +148,8 @@ // ------- this.userRepos = function(username, cb) { - _request("GET", "/users/"+username+"/repos?type=all&per_page=1000&sort=updated", null, function(err, res) { + // Github does not always honor the 1000 limit so we want to iterate over the data set. + _requestAllPages("/users/"+username+"/repos?type=all&per_page=1000&sort=updated", function(err, res) { cb(err, res); }); }; @@ -109,7 +167,8 @@ // ------- this.orgRepos = function(orgname, cb) { - _request("GET", "/orgs/"+orgname+"/repos?type=all&per_page=1000&sort=updated&direction=desc", null, function(err, res) { + // Github does not always honor the 1000 limit so we want to iterate over the data set. + _requestAllPages("/orgs/"+orgname+"/repos?type=all&&page_num=1000&sort=updated&direction=desc", function(err, res) { cb(err, res); }); }; @@ -140,7 +199,7 @@ Github.Repository = function(options) { var repo = options.name; var user = options.user; - + var that = this; var repoPath = "/repos/" + user + "/" + repo; @@ -185,7 +244,7 @@ // Delete a reference // -------- - // + // // repo.deleteRef('heads/gh-pages') // repo.deleteRef('tags/v1.0') @@ -193,6 +252,60 @@ _request("DELETE", repoPath + "/git/refs/"+ref, options, cb); }; + // Create a repo + // ------- + + this.createRepo = function(options, cb) { + _request("POST", "/user/repos", options, cb); + }; + + // Delete a repo + // -------- + + this.deleteRepo = function(cb) { + _request("DELETE", repoPath, options, cb); + }; + + // List all tags of a repository + // ------- + + this.listTags = function(cb) { + _request("GET", repoPath + "/tags", null, function(err, tags) { + if (err) return cb(err); + cb(null, tags); + }); + }; + + // List all pull requests of a respository + // ------- + + this.listPulls = function(state, cb) { + _request("GET", repoPath + "/pulls" + (state ? '?state=' + state : ''), null, function(err, pulls) { + if (err) return cb(err); + cb(null, pulls); + }); + }; + + // Gets details for a specific pull request + // ------- + + this.getPull = function(number, cb) { + _request("GET", repoPath + "/pulls/" + number, null, function(err, pull) { + if (err) return cb(err); + cb(null, pull); + }); + }; + + // Retrieve the changes made between base and head + // ------- + + this.compare = function(base, head, cb) { + _request("GET", repoPath + "/compare/" + base + "..." + head, null, function(err, diff) { + if (err) return cb(err); + cb(null, diff); + }); + }; + // List all branches of a repository // ------- @@ -217,6 +330,7 @@ // Just use head if path is empty if (path === "") return that.getRef("heads/"+branch, cb); that.getTree(branch+"?recursive=true", function(err, tree) { + if (err) return cb(err); var file = _.select(tree, function(file) { return file.path === path; })[0]; @@ -243,7 +357,12 @@ "content": content, "encoding": "utf-8" }; - } + } else { + content = { + "content": btoa(String.fromCharCode.apply(null, new Uint8Array(content))), + "encoding": "base64" + }; + } _request("POST", repoPath + "/git/blobs", content, function(err, res) { if (err) return cb(err); @@ -290,14 +409,16 @@ this.commit = function(parent, tree, message, cb) { var data = { "message": message, - "author": { - "name": options.username - }, "parents": [ parent ], "tree": tree }; + if(options.username) { + data.author = { + "name": options.username + }; + } _request("POST", repoPath + "/git/commits", data, function(err, res) { currentTree.sha = res.sha; // update latest commit @@ -325,8 +446,8 @@ // Get contents // -------- - this.contents = function(branch, path, cb) { - _request("GET", repoPath + "/contents?ref=" + branch, { path: path }, cb); + this.contents = function(branch, path, cb, sync) { + return _request("GET", repoPath + "/contents?ref=" + branch + (path ? "&path=" + path : ""), null, cb, 'raw', sync); }; // Fork repository @@ -336,6 +457,24 @@ _request("POST", repoPath + "/forks", null, cb); }; + // Branch repository + // -------- + + this.branch = function(oldBranch,newBranch,cb) { + if(arguments.length === 2 && typeof arguments[1] === "function") { + cb = newBranch; + newBranch = oldBranch; + oldBranch = "master"; + } + this.getRef("heads/" + oldBranch, function(err,ref) { + if(err && cb) return cb(err); + that.createRef({ + ref: "refs/heads/" + newBranch, + sha: ref + },cb); + }); + } + // Create pull request // -------- @@ -343,6 +482,41 @@ _request("POST", repoPath + "/pulls", options, cb); }; + // List hooks + // -------- + + this.listHooks = function(cb) { + _request("GET", repoPath + "/hooks", null, cb); + }; + + // Get a hook + // -------- + + this.getHook = function(id, cb) { + _request("GET", repoPath + "/hooks/" + id, null, cb); + }; + + // Create a hook + // -------- + + this.createHook = function(options, cb) { + _request("POST", repoPath + "/hooks", options, cb); + }; + + // Edit a hook + // -------- + + this.editHook = function(id, options, cb) { + _request("PATCH", repoPath + "/hooks/" + id, options, cb); + }; + + // Delete a hook + // -------- + + this.deleteHook = function(id, cb) { + _request("DELETE", repoPath + "/hooks/" + id, null, cb); + }; + // Read file at given path // ------- @@ -377,7 +551,24 @@ }); }); }; - + + // Delete a file from the tree + // ------- + + this.delete = function(branch, path, cb) { + that.getSha(branch, path, function(err, sha) { + if (!sha) return cb("not found", null); + var delPath = repoPath + "/contents/" + path; + var params = { + "message": "Deleted " + path, + "sha": sha + }; + delPath += "?message=" + encodeURIComponent(params.message); + delPath += "&sha=" + encodeURIComponent(params.sha); + _request("DELETE", delPath, null, cb); + }) + } + // Move a file to a new location // ------- @@ -419,6 +610,43 @@ }); }); }; + + // List commits on a repository. Takes an object of optional paramaters: + // sha: SHA or branch to start listing commits from + // path: Only commits containing this file path will be returned + // since: ISO 8601 date - only commits after this date will be returned + // until: ISO 8601 date - only commits before this date will be returned + // ------- + + this.getCommits = function(options, cb) { + options = options || {}; + var url = repoPath + "/commits"; + var params = []; + if (options.sha) { + params.push("sha=" + encodeURIComponent(options.sha)); + } + if (options.path) { + params.push("path=" + encodeURIComponent(options.path)); + } + if (options.since) { + var since = options.since; + if (since.constructor === Date) { + since = since.toISOString(); + } + params.push("since=" + encodeURIComponent(since)); + } + if (options.until) { + var until = options.until; + if (until.constructor === Date) { + until = until.toISOString(); + } + params.push("until=" + encodeURIComponent(until)); + } + if (params.length > 0) { + url += "?" + params.join("&"); + } + _request("GET", url, null, cb); + }; }; // Gists API @@ -448,7 +676,7 @@ // } // } // } - + this.create = function(options, cb){ _request("POST","/gists", options, cb); }; @@ -479,11 +707,55 @@ cb(err,res); }); }; + + // Star a gist + // -------- + + this.star = function(cb) { + _request("PUT", gistPath+"/star", null, function(err,res) { + cb(err,res); + }); + }; + + // Untar a gist + // -------- + + this.unstar = function(cb) { + _request("DELETE", gistPath+"/star", null, function(err,res) { + cb(err,res); + }); + }; + + // Check if a gist is starred + // -------- + + this.isStarred = function(cb) { + _request("GET", gistPath+"/star", null, function(err,res) { + cb(err,res); + }); + }; + }; + + // Issues API + // ========== + + Github.Issue = function(options) { + var path = "/repos/" + options.user + "/" + options.repo + "/issues"; + + this.list = function(options, cb) { + _request("GET", path, options, function(err, res) { + cb(err,res) + }); + }; }; // Top Level API // ------- + this.getIssues = function(user, repo) { + return new Github.Issue({user: user, repo: repo}); + }; + this.getRepo = function(user, repo) { return new Github.Repository({user: user, name: repo}); }; @@ -496,4 +768,12 @@ return new Github.Gist({id: id}); }; }; -}).call(this); \ No newline at end of file + + + if (typeof exports !== 'undefined') { + // Github = exports; + module.exports = Github; + } else { + window.Github = Github; + } +}).call(this);