var spawn = require("child_process").spawn; var fs = require("fs"); var sgf = function(filter, callback) { if (typeof filter == "function") { callback = filter; filter = "ACDMRTUXB"; } sgf.getHead(function(err, head) { if (err) { callback(err); } else { var command = "git -c core.quotepath=false diff-index --cached --name-status"; if (filter.indexOf('R') !== -1) { command += " -M"; } command += " --diff-filter=" + filter + " " + head; run(command, function(err, stdout, stderr) { if (err || stderr) { callback(err || new Error(stderr)); } else { callback(null, stdoutToResultsObject(stdout)); } }); } }); } sgf.cwd = process.cwd(); sgf.debug = false; sgf.includeContent = false; // sgf.firstHead = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; sgf.getHead = function(callback) { run("git rev-parse --verify HEAD", function(err, stdout, stderr) { if (err && err.message.indexOf("fatal: Needed a single revision")!==-1) { // callback(null, sgf.firstHead); run("git hash-object -t tree /dev/null", function(err, stdout, stderr) { if (err || stderr) { callback(err || new Error("STDERR: " + stderr)); } else { stdout = stdout.replace("\n", ""); callback(null, stdout); } }); } else if (err || stderr) { callback(err || new Error("STDERR: " + stderr)); } else { stdout = stdout.replace("\n", ""); callback(null, stdout); } }); } sgf.readFile = function(filename, options, callback) { fs.readFile(sgf.cwd + "/" + filename, options, callback); } module.exports = sgf; /** ======================================== HELPERS ======================================== **/ var run = function(command, callback) { if (sgf.debug) { console.log("RUNNING: " + command); } var bits = command.split(" "); var args = bits.slice(1); var cmd = spawn(bits[0], args, { cwd: module.exports.cwd }); var stdout = ""; var stderr = ""; cmd.stdout.on('data', function(data){ stdout+=data.toString(); }); cmd.stderr.on('data', function(data){ stderr+=data.toString(); }); cmd.on("close", function(code){ var err = null; if(code!==0){ err = new Error(stderr); } callback(err,stdout,stderr); }); } var codeToStatus = function(code) { /* ======================================================================================================= ** PER docs at https://git-scm.com/docs/git-diff-index ** Possible status letters are: ** A: addition of a file ** C: copy of a file into a new one ** D: deletion of a file ** M: modification of the contents or mode of a file ** R: renaming of a file ** T: change in the type of the file ** U: file is unmerged (you must complete the merge before it can be committed) ** X: "unknown" change type (most probably a bug, please report it) ** ** Status letters C and R are always followed by a score ** (denoting the percentage of similarity between the source and target of the move or copy). ** Status letter M may be followed by a score (denoting the percentage of dissimilarity) for file rewrites. ** ======================================================================================================= */ var map = { "A": "Added", "C": "Copied", "D": "Deleted", "M": "Modified", "R": "Renamed", "T": "Type-Change", "U": "Unmerged", "X": "Unknown", "B": "Broken" } return map[code.charAt(0)]; } var stdoutToResultsObject = function(stdout) { var results = []; var lines = stdout.split("\n"); var iLines = lines.length; var files_with_errors = 0; while (iLines--) { var line = lines[iLines]; if (line != "") { var parts = line.split("\t"); var result = { filename: parts[2] || parts[1], status: codeToStatus(parts[0]) } if (sgf.includeContent) { try { result.content = fs.readFileSync(sgf.cwd + "/" + result.filename, { encoding: "utf8" }); } catch (err) { result.err = err; } } results.push(result); } } return results; }