var child = require("child_process"),
    mime = require("mime"),
    path = require("path");

// Since we have to exec git rev-parse, make sure the arguments are safe.
var shaRe = /^[0-9a-f]{40}$/,
    revisionRe = /^[0-9a-z]+([_-][0-9a-z]+)*([^~0-9])*$/i,
    repositoryRe = /^[0-9a-z]+([_-][0-9a-z]+)*$/i,
    branchRe = repositoryRe;

function readBlob(repository, revision, file, callback) {
  if (!repositoryRe.test(repository)) return callback(new Error("invalid repository name"));
  if (!revisionRe.test(revision)) return callback(new Error("invalid revision name"));

  var git = child.spawn("git", ["cat-file", "blob", revision + ":" + file], {cwd: repository}),
      data = [],
      exit;

  git.stdout.on("data", function(chunk) {
    data.push(chunk);
  });

  git.on("exit", function(code) {
    exit = code;
  });

  git.on("close", function() {
    if (exit > 0) return callback(error(exit));
    callback(null, Buffer.concat(data));
  });

  git.stdin.end();
}

exports.readBlob = readBlob;

exports.getBranches = function(repository, callback) {
  if (!repositoryRe.test(repository)) return callback(new Error("invalid repository name"));
  child.exec("git branch -l", {cwd: repository}, function(error, stdout) {
    if (error) return callback(error);
    callback(null, stdout.split(/\n/).slice(0, -1).map(function(s) { return s.slice(2); }));
  });
};

exports.getSha = function(repository, revision, callback) {
  if (!repositoryRe.test(repository)) return callback(new Error("invalid repository name"));
  if (!revisionRe.test(revision)) return callback(new Error("invalid revision name"));
  child.exec("git rev-parse \"" + revision + "\"", {cwd: repository}, function(error, stdout) {
    if (error) return callback(error);
    callback(null, stdout.trim());
  });
};

exports.getRelatedCommits = function(repository, branch, sha, callback) {
  if (!repositoryRe.test(repository)) return callback(new Error("invalid repository name"));
  if (!branchRe.test(branch)) return callback(new Error("invalid branch name"));
  if (!shaRe.test(sha)) return callback(new Error("invalid sha name"));
  child.exec("git log --format='%H' \"" + branch + "\" | grep -C1 " + sha, {cwd: repository}, function(error, stdout) {
    if (error) return callback(error);
    var shas = stdout.split(/\n/),
        i = shas.indexOf(sha);

    callback(null, {
      previous: shas[i + 1],
      next: shas[i - 1]
    });
  });
};

exports.route = function() {
  var repository = defaultRepository,
      revision = defaultRevision,
      file = defaultFile,
      type = defaultType;

  function route(request, response) {
    var repository_,
        revision_,
        file_;

    if ((repository_ = repository(request.url)) == null
        || (revision_ = revision(request.url)) == null
        || (file_ = file(request.url)) == null) return serveNotFound();

    readBlob(repository_, revision_, file_, function(error, data) {
      if (error) return error.code === 128 ? serveNotFound() : serveError(error);
      response.statusCode = 200;
      response.setHeader("Content-Type", type(file_));
      response.end(data);
    });

    function serveError(error) {
      response.statusCode = 500;
      response.setHeader("Content-Type", "text/plain");
      response.end(error + "");
    }

    function serveNotFound() {
      response.statusCode = 404;
      response.setHeader("Content-Type", "text/plain");
      response.end("File not found.");
    }
  }

  route.repository = function(_) {
    if (!arguments.length) return repository;
    repository = functor(_);
    return route;
  };

  route.sha = // sha is deprecated; use revision instead
  route.revision = function(_) {
    if (!arguments.length) return revision;
    revision = functor(_);
    return route;
  };

  route.file = function(_) {
    if (!arguments.length) return file;
    file = functor(_);
    return route;
  };

  route.type = function(_) {
    if (!arguments.length) return type;
    type = functor(_);
    return route;
  };

  return route;
};

function functor(_) {
  return typeof _ === "function" ? _ : function() { return _; };
}

function defaultRepository() {
  return path.join(__dirname, "repository");
}

function defaultRevision(url) {
  return decodeURIComponent(url.substring(1, url.indexOf("/", 1)));
}

function defaultFile(url) {
  return decodeURIComponent(url.substring(url.indexOf("/", 1) + 1));
}

function defaultType(file) {
  var type = mime.lookup(file, "text/plain");
  return text(type) ? type + "; charset=utf-8" : type;
}

function text(type) {
  return /^(text\/)|(application\/(javascript|json)|image\/svg$)/.test(type);
}

function error(code) {
  var e = new Error;
  e.code = code;
  return e;
}

@@ -92,490 +92,96 @@
 );%0A%0A
-// Since we have to exec git rev-parse, make sure the arguments are safe.%0Avar shaRe = /%5E%5B0-9a-f%5D%7B40%7D$/,%0A    revisionRe = /%5E%5B0-9a-z%5D+(%5B_-%5D%5B0-9a-z%5D+)*(%5B%5E~0-9%5D)*$/i,%0A    repositoryRe = /%5E%5B0-9a-z%5D+(%5B_-%5D%5B0-9a-z%5D+)*$/i,%0A    branchRe = repositoryRe;%0A%0Afunction readBlob(repository, revision, file, callback) %7B%0A  if (!repositoryRe.test(repository)) return callback(new Error(%22invalid repository name%22));%0A  if (!revisionRe.test(revision)) return callback(new Error(%22invalid revision name%22));%0A
+var shaRe = /%5E%5B0-9a-f%5D%7B40%7D$/;%0A%0Afunction readBlob(repository, revision, file, callback) %7B
 %0A  v
@@ -670,101 +670,8 @@
 ) %7B%0A
-  if (!repositoryRe.test(repository)) return callback(new Error(%22invalid repository name%22));%0A
   ch
@@ -947,188 +947,8 @@
 ) %7B%0A
-  if (!repositoryRe.test(repository)) return callback(new Error(%22invalid repository name%22));%0A  if (!revisionRe.test(revision)) return callback(new Error(%22invalid revision name%22));%0A
   ch
@@ -971,18 +971,17 @@
 v-parse 
-%5C%22
+'
 %22 + revi
@@ -984,22 +984,43 @@
 revision
- + %22%5C%22
+.replace(/'/g, %22'%5C''%22) + %22'
 %22, %7Bcwd:
@@ -1221,182 +1221,8 @@
 ) %7B%0A
-  if (!repositoryRe.test(repository)) return callback(new Error(%22invalid repository name%22));%0A  if (!branchRe.test(branch)) return callback(new Error(%22invalid branch name%22));%0A
   if
@@ -1280,16 +1280,11 @@
 lid 
-sha name
+SHA
 %22));
@@ -1320,18 +1320,17 @@
 at='%25H' 
-%5C%22
+'
 %22 + bran
@@ -1335,14 +1335,35 @@
 anch
- + %22%5C%22
+.replace(/'/g, %22'%5C''%22) + %22'
  %7C g

Replace name restrictions with proper quoting.