// Github.js 0.7.0 // (c) 2012 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; var API_URL = 'https://api.github.com'; Github = window.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 getURL() { var url = 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.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' ? 'token '+ options.token : 'Basic ' + Base64.encode(options.username + ':' + options.password) ); } data ? xhr.send(JSON.stringify(data)) : xhr.send(); } // User API // ======= Github.User = function() { this.repos = function(cb) { _request("GET", "/user/repos?type=all&per_page=1000&sort=updated", null, function(err, res) { cb(err, res); }); }; // List user organizations // ------- this.orgs = function(cb) { _request("GET", "/user/orgs", null, function(err, res) { cb(err, res); }); }; // List authenticated user's gists // ------- this.gists = function(cb) { _request("GET", "/gists", null, function(err, res) { cb(err,res); }); }; // Show user information // ------- this.show = function(username, cb) { var command = username ? "/users/"+username : "/user"; _request("GET", command, null, function(err, res) { cb(err, res); }); }; // List user repositories // ------- this.userRepos = function(username, cb) { _request("GET", "/users/"+username+"/repos?type=all&per_page=1000&sort=updated", null, function(err, res) { cb(err, res); }); }; // List a user's gists // ------- this.userGists = function(username, cb) { _request("GET", "/users/"+username+"/gists", null, function(err, res) { cb(err,res); }); }; // List organization repositories // ------- this.orgRepos = function(orgname, cb) { _request("GET", "/orgs/"+orgname+"/repos?type=all&per_page=1000&sort=updated&direction=desc", null, function(err, res) { cb(err, res); }); }; // Follow user // ------- this.follow = function(username, cb) { _request("PUT", "/user/following/"+username, null, function(err, res) { cb(err, res); }); }; // Unfollow user // ------- this.unfollow = function(username, cb) { _request("DELETE", "/user/following/"+username, null, function(err, res) { cb(err, res); }); }; }; // Repository API // ======= Github.Repository = function(options) { var repo = options.name; var user = options.user; var that = this; var repoPath = "/repos/" + user + "/" + repo; var currentTree = { "branch": null, "sha": null }; // Uses the cache if branch has not been changed // ------- function updateTree(branch, cb) { if (branch === currentTree.branch && currentTree.sha) return cb(null, currentTree.sha); that.getRef("heads/"+branch, function(err, sha) { currentTree.branch = branch; currentTree.sha = sha; cb(err, sha); }); } // Get a particular reference // ------- this.getRef = function(ref, cb) { _request("GET", repoPath + "/git/refs/" + ref, null, function(err, res) { if (err) return cb(err); cb(null, res.object.sha); }); }; // Create a new reference // -------- // // { // "ref": "refs/heads/my-new-branch-name", // "sha": "827efc6d56897b048c772eb4087f854f46256132" // } this.createRef = function(options, cb) { _request("POST", repoPath + "/git/refs", options, cb); }; // Delete a reference // -------- // // repo.deleteRef('heads/gh-pages') // repo.deleteRef('tags/v1.0') this.deleteRef = function(ref, cb) { _request("DELETE", repoPath + "/git/refs/"+ref, options, cb); }; // List all branches of a repository // ------- this.listBranches = function(cb) { _request("GET", repoPath + "/git/refs/heads", null, function(err, heads) { if (err) return cb(err); cb(null, _.map(heads, function(head) { return _.last(head.ref.split('/')); })); }); }; // Retrieve the contents of a blob // ------- this.getBlob = function(sha, cb) { _request("GET", repoPath + "/git/blobs/" + sha, null, cb, 'raw'); }; // For a given file path, get the corresponding sha (blob for files, tree for dirs) // ------- this.getSha = function(branch, path, cb) { // Just use head if path is empty if (path === "") return that.getRef("heads/"+branch, cb); that.getTree(branch+"?recursive=true", function(err, tree) { var file = _.select(tree, function(file) { return file.path === path; })[0]; cb(null, file ? file.sha : null); }); }; // Retrieve the tree a commit points to // ------- this.getTree = function(tree, cb) { _request("GET", repoPath + "/git/trees/"+tree, null, function(err, res) { if (err) return cb(err); cb(null, res.tree); }); }; // Post a new blob object, getting a blob SHA back // ------- this.postBlob = function(content, cb) { if (typeof(content) === "string") { content = { "content": content, "encoding": "utf-8" }; } _request("POST", repoPath + "/git/blobs", content, function(err, res) { if (err) return cb(err); cb(null, res.sha); }); }; // Update an existing tree adding a new blob object getting a tree SHA back // ------- this.updateTree = function(baseTree, path, blob, cb) { var data = { "base_tree": baseTree, "tree": [ { "path": path, "mode": "100644", "type": "blob", "sha": blob } ] }; _request("POST", repoPath + "/git/trees", data, function(err, res) { if (err) return cb(err); cb(null, res.sha); }); }; // Post a new tree object having a file path pointer replaced // with a new blob SHA getting a tree SHA back // ------- this.postTree = function(tree, cb) { _request("POST", repoPath + "/git/trees", { "tree": tree }, function(err, res) { if (err) return cb(err); cb(null, res.sha); }); }; // Create a new commit object with the current commit SHA as the parent // and the new tree SHA, getting a commit SHA back // ------- this.commit = function(parent, tree, message, cb) { var data = { "message": message, "author": { "name": options.username }, "parents": [ parent ], "tree": tree }; _request("POST", repoPath + "/git/commits", data, function(err, res) { currentTree.sha = res.sha; // update latest commit if (err) return cb(err); cb(null, res.sha); }); }; // Update the reference of your head to point to the new commit SHA // ------- this.updateHead = function(head, commit, cb) { _request("PATCH", repoPath + "/git/refs/heads/" + head, { "sha": commit }, function(err, res) { cb(err); }); }; // Show repository information // ------- this.show = function(cb) { _request("GET", repoPath, null, cb); }; // Get contents // -------- this.contents = function(branch, path, cb) { _request("GET", repoPath + "/contents?ref=" + branch, { path: path }, cb); }; // Fork repository // ------- this.fork = function(cb) { _request("POST", repoPath + "/forks", null, cb); }; // Create pull request // -------- this.createPullRequest = function(options, cb) { _request("POST", repoPath + "/pulls", options, cb); }; // Read file at given path // ------- this.read = function(branch, path, cb) { that.getSha(branch, path, function(err, sha) { if (!sha) return cb("not found", null); that.getBlob(sha, function(err, content) { cb(err, content, sha); }); }); }; // Remove a file from the tree // ------- this.remove = function(branch, path, cb) { updateTree(branch, function(err, latestCommit) { that.getTree(latestCommit+"?recursive=true", function(err, tree) { // Update Tree var newTree = _.reject(tree, function(ref) { return ref.path === path; }); _.each(newTree, function(ref) { if (ref.type === "tree") delete ref.sha; }); that.postTree(newTree, function(err, rootTree) { that.commit(latestCommit, rootTree, 'Deleted '+path , function(err, commit) { that.updateHead(branch, commit, function(err) { cb(err); }); }); }); }); }); }; // Move a file to a new location // ------- this.move = function(branch, path, newPath, cb) { updateTree(branch, function(err, latestCommit) { that.getTree(latestCommit+"?recursive=true", function(err, tree) { // Update Tree _.each(tree, function(ref) { if (ref.path === path) ref.path = newPath; if (ref.type === "tree") delete ref.sha; }); that.postTree(tree, function(err, rootTree) { that.commit(latestCommit, rootTree, 'Deleted '+path , function(err, commit) { that.updateHead(branch, commit, function(err) { cb(err); }); }); }); }); }); }; // Write file contents to a given branch and path // ------- this.write = function(branch, path, content, message, cb) { updateTree(branch, function(err, latestCommit) { if (err) return cb(err); that.postBlob(content, function(err, blob) { if (err) return cb(err); that.updateTree(latestCommit, path, blob, function(err, tree) { if (err) return cb(err); that.commit(latestCommit, tree, message, function(err, commit) { if (err) return cb(err); that.updateHead(branch, commit, cb); }); }); }); }); }; }; // Gists API // ======= Github.Gist = function(options) { var id = options.id; var gistPath = "/gists/"+id; // Read the gist // -------- this.read = function(cb) { _request("GET", gistPath, null, function(err, gist) { cb(err, gist); }); }; // Create the gist // -------- // { // "description": "the description for this gist", // "public": true, // "files": { // "file1.txt": { // "content": "String file contents" // } // } // } this.create = function(options, cb){ _request("POST","/gists", options, cb); }; // Delete the gist // -------- this.delete = function(cb) { _request("DELETE", gistPath, null, function(err,res) { cb(err,res); }); }; // Fork a gist // -------- this.fork = function(cb) { _request("POST", gistPath+"/fork", null, function(err,res) { cb(err,res); }); }; // Update a gist with the new stuff // -------- this.update = function(options, cb) { _request("PATCH", gistPath, options, function(err,res) { cb(err,res); }); }; }; // Top Level API // ------- this.getRepo = function(user, repo) { return new Github.Repository({user: user, name: repo}); }; this.getUser = function() { return new Github.User(); }; this.getGist = function(id) { return new Github.Gist({id: id}); }; }; }).call(this);