diff options
-rw-r--r-- | api/git.js | 89 | ||||
-rw-r--r-- | api/sanitization.js | 14 | ||||
-rw-r--r-- | api/v1.js | 23 | ||||
-rw-r--r-- | app.js | 13 |
4 files changed, 126 insertions, 13 deletions
@@ -17,7 +17,7 @@ function execGit(path, action, args = [""], error) error({ "error": err }); }); - git.stderr.on("data", () => error({ "error": "Failed to communicate with git!" })); + git.stderr.on("data", (err) => error({ "error": "Failed to communicate with git!", "message": err.toString() })); return git; } @@ -142,7 +142,92 @@ function getRepos(base_dir) }); } +function getCommit(base_dir, repo, hash) +{ + return new Promise((resolve) => + { + const git = execGit(`${base_dir}/${repo}`, "show", ['--format=format:\"hash\": \"%H\, \"author\": \"%an <%ae>\", \"date\": \"%at\", \"message\": \"%s\", \"diff\": \"', hash], (err) => resolve(err)); + + let commit = []; + + git.stdout.on("data", (data) => + { + commit.push(data); + }); + + git.on("close", () => + { + let diff = commit.toString().split('\n').slice(1); + + var result = []; + + let start; + diff.forEach((line, index) => + { + if(/^diff\ --git a\/[^\ ]+\ b\/[^\ ]+$/.test(line) || index === diff.length - 1) { + if(start) { + let file_diff = diff.slice(start, index - 1); + let chunk_header_index = file_diff.findIndex((line) => /^@@\ -[0-9,]+\ \+[0-9,]+\ @@/.test(line)); + + let file_info = {}; + + let header = file_diff.slice(1, chunk_header_index - 2); + const from_to = file_diff.slice(chunk_header_index - 2, chunk_header_index); + + file_info["from"] = from_to[0]; + file_info["to"] = from_to[1]; + + const chunk_header = /^@@\ (-[0-9,]+)\ (\+[0-9,]+)\ @@(?:\ (.*))?/.exec(file_diff[chunk_header_index]); + + file_info["from_file_range"] = chunk_header[1]; + file_info["to_file_range"] = chunk_header[2]; + + file_info["diff"] = file_diff.slice(chunk_header_index + 1).join("\n"); + + if(chunk_header[3]) { + file_info["diff"] = chunk_header[3] + file_info["diff"]; + } + + header.forEach((line) => + { + if(line.includes("old mode") || line.includes("new mode") || line.includes("deleted file mode") || line.includes("new file mode")) { + const data = /^(.*mode)\ (\d{6})$/.exec(line); + file_info[data[1].replaceAll(' ', "_")] = data[2]; + } + else if(line.includes("copy from") || line.includes("copy to")) { + const data = /^(copy\ from|to)\ (.*)/.exec(line); + file_info[data[1].replaceAll(' ', "_")] = data[2]; + } + else if(line.includes("rename from") || line.includes("rename to")) { + const data = /^(rename\ from|to)\ (.*)/.exec(line); + file_info[data[1].replaceAll(' ', "_")] = data[2]; + } + else if(line.includes("similarity index") || line.includes("dissimilarity index")) { + const data = /^((?:dis)?similarity\ index)\ (\d+%)$/.exec(line); + file_info[data[1].replaceAll(' ', "_")] = data[2]; + } + else if(line.includes("index")) { + const data = /^index\ ([0-9a-f,]+)\.\.([0-9a-f,]+)(?:\ (\d{6}))?$/.exec(line).slice(1); + file_info["index"] = { "before": data[0], "after": data[1] }; + if(data[2]) { + file_info["index"]["mode"] = data[2]; + } + } + }); + result.push(file_info); + } + start = index; + } + if(index === diff.length - 1) { + resolve({ "data": result }); + } + }); + }); + }) +} + module.exports.getLog = getLog; module.exports.getBasicRepoInfo = getBasicRepoInfo; module.exports.getRepos = getRepos; -module.exports.getRepoFile = getRepoFile;
\ No newline at end of file +module.exports.getRepoFile = getRepoFile; +module.exports.getCommit = getCommit;
\ No newline at end of file diff --git a/api/sanitization.js b/api/sanitization.js index 5c9d009..95c8810 100644 --- a/api/sanitization.js +++ b/api/sanitization.js @@ -1,10 +1,14 @@ function sanitizeRepoName(dirty) { const valid_repo_name = /^[a-zA-Z0-9\.\-_]+$/; - if(valid_repo_name.test(dirty)) { - return true; - } - return false; + return valid_repo_name.test(dirty); } -module.exports.sanitizeRepoName = sanitizeRepoName;
\ No newline at end of file +function sanitizeCommitID(dirty) +{ + const valid_commit_id = /^[a-fA-F0-9]{40}$/; + return valid_commit_id.test(dirty); +} + +module.exports.sanitizeRepoName = sanitizeRepoName; +module.exports.sanitizeCommitID = sanitizeCommitID;
\ No newline at end of file @@ -24,12 +24,17 @@ router.get("/repos", async function(req, res) res.json({ "data": repos }); }); -router.get("/repos/:repo", async function(req, res) +router.use("/repos/:repo", async function(req, res, next) { if(!sanitization.sanitizeRepoName(req.params.repo)) { res.status(400).json({ "error": "Unacceptable git repository name!" }); return; } + next(); +}); + +router.get("/repos/:repo", async function(req, res) +{ const repo = `${req.params.repo}.git`; const desc = await git.getRepoFile(req.settings["base_dir"], repo, "description"); @@ -38,10 +43,6 @@ router.get("/repos/:repo", async function(req, res) router.get("/repos/:repo/log", async function(req, res) { - if(!sanitization.sanitizeRepoName(req.params.repo)) { - res.status(400).json({ "error": "Unacceptable git repository name!" }); - return; - } const repo = `${req.params.repo}.git`; const log = await git.getLog(req.settings["base_dir"], repo); @@ -60,4 +61,16 @@ router.get("/repos/:repo/log", async function(req, res) res.json(log); }); +router.get("/repos/:repo/log/:commit", async function(req, res) +{ + if(!sanitization.sanitizeCommitID(req.params.commit)) { + res.status(400).json({ "error": "Unacceptable commit id!" }); + return; + } + + const commit = await git.getCommit(req.settings["base_dir"], req.params.repo, req.params.commit); + + res.json(commit); +}); + module.exports = router;
\ No newline at end of file @@ -56,7 +56,7 @@ app.use("/:repo", async (req, res, next) => next(); }) -app.get("/:repo", (req, res, next) => +app.get("/:repo", (req, res) => { res.redirect(`/${req.params.repo}/log`); }); @@ -72,6 +72,17 @@ app.get("/:repo/:page", (req, res, next) => res.sendFile("dist/app.html", { root: __dirname }); }); +app.get("/:repo/log/:hash", (req, res, next) => +{ + const valid_hash = /^[0-9a-f]+$/; + if(!valid_hash.test(req.params.hash)) { + next(); + return; + } + res.sendFile("dist/app.html", { root: __dirname }); +}); + + app.get("/", (req, res) => { res.sendFile("dist/app.html", { root: __dirname }); |