<commit_before>/*!
 * @overview  Github.js
 *
 * @copyright (c) 2013 <NAME>, Development Seed
 *            Github.js is freely distributable.
 *
 * @license   Licensed under MIT license
 *
 *            For all details and documentation:
 *            http://substance.io/michael/github
 */

(function() {
  'use strict';
  
  // Initial Setup
  // -------------

  var XMLHttpRequest,  _;
  /* istanbul ignore else  */
  if (typeof exports !== 'undefined') {
      XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
      _ = require('underscore');
      if (typeof btoa === 'undefined') {
        var btoa = require('btoa'); //jshint ignore:line
      }
      
  } else { 
      _ = window._; 
  }
  
  //prefer native XMLHttpRequest always
  /* istanbul ignore if  */
  if (typeof window !== 'undefined' && typeof window.XMLHttpRequest !== 'undefined'){
      XMLHttpRequest = window.XMLHttpRequest;
  }



  var Github = function(options) {
    var API_URL = options.apiUrl || 'https://api.github.com';

    // 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, sync) {
      function getURL() {
        var url = path.indexOf('//') >= 0 ? path : API_URL + path;
        return url + ((/\?/).test(url) ? '&' : '?') + (new Date()).getTime();
      }

      var xhr = new XMLHttpRequest();


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

      if (!raw) {
        xhr.dataType = 'json';
        xhr.setRequestHeader('Accept','application/vnd.github.v3+json');
      } else {
        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)) {
        var authorization = options.token ? 'token ' + options.token : 'Basic ' + btoa(options.username + ':' + options.password);
        xhr.setRequestHeader('Authorization', authorization);
      }
      if (data) {
        xhr.send(JSON.stringify(data));
      } else {
        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) {
        // 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);
        });
      };

      // 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);
        });
      };

      // List authenticated user's unread notifications
      // -------

      this.notifications = function(cb) {
        _request("GET", '/notifications', 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) {
        // 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);
        });
      };

      // 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) {
        // 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);
        });
      };

      // 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);
        });
      };

      // Create a repo
      // -------
      this.createRepo = function(options, cb) {
        _request('POST', '/user/repos', options, cb);
      };

    };

    // 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
      };


      // Delete a repo
      // --------

      this.deleteRepo = function(cb) {
        _request('DELETE', repoPath, options, cb);
      };

      // 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);
      };

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

      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.getCommit = function(branch, sha, cb) {
        _request("GET", repoPath + "/git/commits/"+sha, null, function(err, commit) {
          if (err) return cb(err);
          cb(null, commit);
        });
      };

      // For a given file path, get the corresponding sha (blob for files, tree for dirs)
      // -------

      this.getSha = function(branch, path, cb) {
        if (!path || path === "") return that.getRef("heads/"+branch, cb);
        _request("GET", repoPath + "/contents/"+path, {ref: branch}, function(err, pathContent) {
          if (err) return cb(err);
          cb(null, pathContent.sha);
        });
      };

      // 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"
          };
        } 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);
          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 user = new Github.User();
        user.show(null, function(err, userData){
          if (err) return cb(err);
          var data = {
            "message": message,
            "author": {
              "name": options.user,
              "email": userData.email
            },
            "parents": [
              parent
            ],
            "tree": tree
          };
          _request("POST", repoPath + "/git/commits", data, function(err, res) {
            if (err) return cb(err);
            currentTree.sha = res.sha; // update latest commit
            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) {
          cb(err);
        });
      };

      // Show repository information
      // -------

      this.show = function(cb) {
        _request("GET", repoPath, null, cb);
      };

      // Show repository contributors
      // -------

      this.contributors = function (cb, retry) {
        retry = retry || 1000;
        var self = this;
        _request("GET", repoPath + "/stats/contributors", null, function (err, data, response) {
          if (err) return cb(err);
          if (response.status === 202) {
            setTimeout(
              function () {
                self.contributors(cb, retry);
              },
              retry
            );
          } else {
            cb(err, data);
          }
        });
      };

      // Get contents
      // --------

      this.contents = function(ref, path, cb) {
        path = encodeURI(path);
        _request("GET", repoPath + "/contents" + (path ? "/" + path : ""), { ref: ref }, cb);
      };

      // Fork repository
      // -------

      this.fork = function(cb) {
        _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
      // --------

      this.createPullRequest = function(options, cb) {
        _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
      // -------

      this.read = function(branch, path, cb) {
        _request("GET", repoPath + "/contents/"+encodeURI(path), {ref: branch}, function(err, obj) {
          if (err && err.error === 404) return cb("not found", null, null);

          if (err) return cb(err);
          cb(null, obj);
        }, true);
      };


      // Remove a file
      // -------

      this.remove = function(branch, path, cb) {
        that.getSha(branch, path, function(err, sha) {
          if (err) return cb(err);
          _request("DELETE", repoPath + "/contents/" + path, {
            message: path + " is removed",
            sha: sha,
            branch: branch
          }, cb);
        });
      };

      // 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);
          delPath += '&branch=' + encodeURIComponent(branch);
          _request("DELETE", delPath, null, cb);
        });
      };

      // 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) {
        that.getSha(branch, encodeURI(path), function(err, sha) {
          if (err && err.error !== 404) return cb(err);
          _request("PUT", repoPath + "/contents/" + encodeURI(path), {
            message: message,
            content: btoa(content),
            branch: branch,
            sha: sha
          }, cb);
        });
      };

      // 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 (options.page) {
              params.push("page=" + options.page);
          }
          if (options.perpage) {
              params.push("per_page=" + options.perpage);
          }
          if (params.length > 0) {
              url += "?" + params.join("&");
          }
          _request("GET", url, null, cb);
      };
    };<commit_msg>Some corrections to Commits, and added Comments.<commit_after>/*!
 * @overview  Github.js
 *
 * @copyright (c) 2013 <NAME>reiter, Development Seed
 *            Github.js is freely distributable.
 *
 * @license   Licensed under MIT license
 *
 *            For all details and documentation:
 *            http://substance.io/michael/github
 */

(function() {
  'use strict';
  
  // Initial Setup
  // -------------

  var XMLHttpRequest,  _;
  /* istanbul ignore else  */
  if (typeof exports !== 'undefined') {
      XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
      _ = require('underscore');
      if (typeof btoa === 'undefined') {
        var btoa = require('btoa'); //jshint ignore:line
      }
      
  } else { 
      _ = window._; 
  }
  
  //prefer native XMLHttpRequest always
  /* istanbul ignore if  */
  if (typeof window !== 'undefined' && typeof window.XMLHttpRequest !== 'undefined'){
      XMLHttpRequest = window.XMLHttpRequest;
  }



  var Github = function(options) {
    options = options || {}
    var API_URL = options.apiUrl || 'https://api.github.com';

    // 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, sync) {
      function getURL() {
        var url = path.indexOf('//') >= 0 ? path : API_URL + path;
        return url + ((/\?/).test(url) ? '&' : '?') + (new Date()).getTime();
      }

      var xhr = new XMLHttpRequest();


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

      if (!raw) {
        xhr.dataType = 'json';
        xhr.setRequestHeader('Accept','application/vnd.github.v3+json');
      } else {
        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)) {
        var authorization = options.token ? 'token ' + options.token : 'Basic ' + btoa(options.username + ':' + options.password);
        xhr.setRequestHeader('Authorization', authorization);
      }
      if (data) {
        xhr.send(JSON.stringify(data));
      } else {
        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) {
        // 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);
        });
      };

      // 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);
        });
      };

      // List authenticated user's unread notifications
      // -------

      this.notifications = function(cb) {
        _request("GET", '/notifications', 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) {
        // 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);
        });
      };

      // 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) {
        // 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);
        });
      };

      // 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);
        });
      };

      // Create a repo
      // -------
      this.createRepo = function(options, cb) {
        _request('POST', '/user/repos', options, cb);
      };

    };

    // 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
      };


      // Delete a repo
      // --------

      this.deleteRepo = function(cb) {
        _request('DELETE', repoPath, options, cb);
      };

      // 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);
      };

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

      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.getCommit = function(sha, cb) {
        _request("GET", repoPath + "/commits/"+sha, null, cb);
      };

      // For a given file path, get the corresponding sha (blob for files, tree for dirs)
      // -------

      this.getSha = function(branch, path, cb) {
        if (!path || path === "") return that.getRef("heads/"+branch, cb);
        _request("GET", repoPath + "/contents/"+path, {ref: branch}, function(err, pathContent) {
          if (err) return cb(err);
          cb(null, pathContent.sha);
        });
      };

      // 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"
          };
        } 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);
          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 user = new Github.User();
        user.show(null, function(err, userData){
          if (err) return cb(err);
          var data = {
            "message": message,
            "author": {
              "name": options.user,
              "email": userData.email
            },
            "parents": [
              parent
            ],
            "tree": tree
          };
          _request("POST", repoPath + "/git/commits", data, function(err, res) {
            if (err) return cb(err);
            currentTree.sha = res.sha; // update latest commit
            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) {
          cb(err);
        });
      };

      // Show repository information
      // -------

      this.show = function(cb) {
        _request("GET", repoPath, null, cb);
      };

      // Show repository contributors
      // -------

      this.contributors = function (cb, retry) {
        retry = retry || 1000;
        var self = this;
        _request("GET", repoPath + "/stats/contributors", null, function (err, data, response) {
          if (err) return cb(err);
          if (response.status === 202) {
            setTimeout(
              function () {
                self.contributors(cb, retry);
              },
              retry
            );
          } else {
            cb(err, data);
          }
        });
      };

      // Get contents
      // --------

      this.contents = function(ref, path, cb) {
        path = encodeURI(path);
        _request("GET", repoPath + "/contents" + (path ? "/" + path : ""), { ref: ref }, cb);
      };

      // Fork repository
      // -------

      this.fork = function(cb) {
        _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
      // --------

      this.createPullRequest = function(options, cb) {
        _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
      // -------

      this.read = function(branch, path, cb) {
        _request("GET", repoPath + "/contents/"+encodeURI(path), {ref: branch}, function(err, obj) {
          if (err && err.error === 404) return cb("not found", null, null);

          if (err) return cb(err);
          cb(null, obj);
        }, true);
      };


      // Remove a file
      // -------

      this.remove = function(branch, path, cb) {
        that.getSha(branch, path, function(err, sha) {
          if (err) return cb(err);
          _request("DELETE", repoPath + "/contents/" + path, {
            message: path + " is removed",
            sha: sha,
            branch: branch
          }, cb);
        });
      };

      // 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);
          delPath += '&branch=' + encodeURIComponent(branch);
          _request("DELETE", delPath, null, cb);
        });
      };

      // 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) {
        that.getSha(branch, encodeURI(path), function(err, sha) {
          if (err && err.error !== 404) return cb(err);
          _request("PUT", repoPath + "/contents/" + encodeURI(path), {
            message: message,
            content: btoa(content),
            branch: branch,
            sha: sha
          }, cb);
        });
      };

      // 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) {
          if(arguments.length === 1 && typeof arguments[0] === "function") {
            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 (options.page) {
              params.push("page=" + options.page);
          }
          if (options.perpage) {
              params.push("per_page=" + options.perpage);
          }
          if (params.length > 0) {
              url += "?" + params.join("&");
          }
          _request("GET", url, null, cb);
      };

      // List commit comments.
      // -------

      this.getComments = function(sha, cb) {
        var commentsPath;
        if(arguments.length === 1 && typeof arguments[0] === "function") {
          cb = sha;
          sha = null;
          commentsPath = '/comments';
        } else {
          commentsPath = '/commits/'+sha+'/comments';
        }

        _request("GET", repoPath + commentsPath, null, cb);
      }

      // Create a commit comment
      // -------

      this.createComment = function(sha, body, options, cb) {
        if(arguments.length === 3 && typeof arguments[2] === "function") {
          cb = options;
          options = {}
        }
        options.body = body;
        _request("POST", repoPath + '/commits/' + sha + '/comments', options, cb);
      }

      // Update a comment
      // -------

      this.updateComment = function(id, body, cb) {
        _request("PATCH", repoPath + '/comments/' + id, {body: body}, cb);
      }

      // Delete a comment
      // -------

      this.deleteComment = function(id, cb) {
        _request("DELETE", repoPath + '/comments/' + id, null, function(){});
      }

    };
{'fraction_non_alphanumeric': 0.16444953034854337, 'fraction_numerical': 0.0053401993038668764, 'mean_word_length': 1.5453276699029126, 'pattern_counts': {'":': 44, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2292507', 'n_tokens_mistral': 12466, 'n_tokens_neox': 11885, 'n_words': 3003}