aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/git.js89
-rw-r--r--api/sanitization.js14
-rw-r--r--api/v1.js23
-rw-r--r--app.js13
4 files changed, 126 insertions, 13 deletions
diff --git a/api/git.js b/api/git.js
index a44356e..3859fe7 100644
--- a/api/git.js
+++ b/api/git.js
@@ -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
diff --git a/api/v1.js b/api/v1.js
index 7358d3b..4987ba7 100644
--- a/api/v1.js
+++ b/api/v1.js
@@ -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
diff --git a/app.js b/app.js
index 51c8958..1a6f8ab 100644
--- a/app.js
+++ b/app.js
@@ -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 });