aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/git.js237
-rw-r--r--src/api/sanitization.js14
-rw-r--r--src/api/v1.js74
-rw-r--r--src/app.js101
-rw-r--r--src/frontend/App.vue (renamed from src/App.vue)0
-rw-r--r--src/frontend/app.html (renamed from src/app.html)0
-rw-r--r--src/frontend/app.js7
-rw-r--r--src/frontend/components/BaseBackButton.vue (renamed from src/components/BaseBackButton.vue)0
-rw-r--r--src/frontend/components/CommitPatch.vue (renamed from src/components/CommitPatch.vue)0
-rw-r--r--src/frontend/components/HomeHeader.vue (renamed from src/components/HomeHeader.vue)0
-rw-r--r--src/frontend/components/RepositoryHeader.vue (renamed from src/components/RepositoryHeader.vue)0
-rw-r--r--src/frontend/components/RepositoryNavbar.vue (renamed from src/components/RepositoryNavbar.vue)0
-rw-r--r--src/frontend/router/index.js (renamed from src/router/index.js)0
-rw-r--r--src/frontend/scss/abstracts/_colors.scss (renamed from src/scss/abstracts/_colors.scss)0
-rw-r--r--src/frontend/scss/abstracts/_fonts.scss (renamed from src/scss/abstracts/_fonts.scss)0
-rw-r--r--src/frontend/scss/style.scss (renamed from src/scss/style.scss)24
-rw-r--r--src/frontend/util/hljs-languages.js (renamed from src/util/hljs-languages.js)0
-rw-r--r--src/frontend/views/Home.vue (renamed from src/views/Home.vue)0
-rw-r--r--src/frontend/views/Repository.vue (renamed from src/views/Repository.vue)0
-rw-r--r--src/frontend/views/RepositoryCommit.vue (renamed from src/views/RepositoryCommit.vue)0
-rw-r--r--src/frontend/views/RepositoryLog.vue (renamed from src/views/RepositoryLog.vue)0
21 files changed, 439 insertions, 18 deletions
diff --git a/src/api/git.js b/src/api/git.js
new file mode 100644
index 0000000..dad7cc6
--- /dev/null
+++ b/src/api/git.js
@@ -0,0 +1,237 @@
+const { formatDistance } = require('date-fns');
+const fs = require('fs');
+const git = require("nodegit");
+
+function addRepoDirSuffix(repo_name)
+{
+ if(!repo_name.endsWith(".git")) {
+ return repo_name + ".git";
+ }
+ return repo_name;
+}
+
+async function getLog(base_dir, repo_name)
+{
+ const repo = await git.Repository.openBare(`${base_dir}/${repo_name}`)
+
+ const walker = git.Revwalk.create(repo);
+ walker.pushHead();
+
+ const raw_commits = await walker.getCommits();
+
+ const commits = Promise.all(raw_commits.map(async commit => ({
+ commit: commit.sha(),
+ author_full: commit.author().toString(),
+ author_name: commit.author().name(),
+ author_email: commit.author().email(),
+ date: commit.date(),
+ message: commit.message().replace(/\n/g, ""),
+ insertions: (await (await commit.getDiff())[0].getStats()).insertions(),
+ deletions: (await (await commit.getDiff())[0].getStats()).deletions(),
+ files_changed: (await (await commit.getDiff())[0].getStats()).filesChanged()
+ })));
+
+ return await commits;
+}
+
+async function getTimeSinceLatestCommit(base_dir, repo_name)
+{
+ const repo = await git.Repository.openBare(`${base_dir}/${repo_name}`)
+ const master_commit = await repo.getMasterCommit();
+
+ return formatDistance(new Date(), master_commit.date());
+}
+
+function getRepoFile(base_dir, repo, file)
+{
+ return new Promise(resolve =>
+ {
+ fs.readFile(`${base_dir}/${repo}/${file}`, async (err, content) =>
+ {
+ if(!err) {
+ resolve(content.toString().replace(/\n/g, ""));
+ return;
+ }
+ resolve("");
+ });
+ });
+}
+
+function getRepos(base_dir)
+{
+ return new Promise((resolve) =>
+ {
+ fs.readdir(base_dir, (err, dir_content) =>
+ {
+ if(err) {
+ resolve({ "error": err });
+ return;
+ }
+
+ dir_content.filter(repo => repo.endsWith(".git")).reduce((acc, repo) =>
+ {
+ return acc.then((repos) =>
+ {
+ return getRepoFile(base_dir, repo, "description").then((description) =>
+ {
+ return getRepoFile(base_dir, repo, "owner").then((owner) =>
+ {
+ return getTimeSinceLatestCommit(base_dir, repo).then((last_commit_date) =>
+ {
+ repos[repo.slice(0, -4)] = { "description": description, "owner": owner, "last_updated": last_commit_date };
+ return repos;
+ });
+ });
+ });
+ });
+ }, Promise.resolve({})).then((repos) =>
+ {
+ resolve(repos);
+ });
+ });
+ });
+}
+
+function parseHunkAddDel(hunk)
+{
+ let new_lines = [];
+ let deleted_lines = [];
+
+ hunk.forEach((line, index) =>
+ {
+ if(line.charAt(0) === '+') {
+ hunk[index] = line.slice(1);
+ new_lines.push(index);
+ }
+ else if(line.charAt(0) === '-') {
+ hunk[index] = line.slice(1);
+ deleted_lines.push(index);
+ }
+ });
+
+ return { new: new_lines, deleted: deleted_lines, hunk: hunk.join("\n") };
+}
+
+async function getCommit(base_dir, repo_name, commit_oid)
+{
+ repo_name = addRepoDirSuffix(repo_name);
+
+ const repo = await git.Repository.openBare(`${base_dir}/${repo_name}`)
+ const commit = await repo.getCommit(commit_oid);
+ const diff = (await commit.getDiff())[0];
+ const all_patches = (await diff.toBuf(1)).split('\n');
+
+ // Get the count of lines for all of patches's headers
+ const patch_headers = (await diff.toBuf(2)).split('\n');
+ const patch_header_data = await patch_headers.reduce((acc, line, index) =>
+ {
+ return acc.then((arr) =>
+ {
+ if(/^diff --git/.test(line)) {
+ arr[0].push(all_patches.indexOf(line));
+
+ if(arr[2] != undefined) {
+ arr[1].push(patch_headers.slice(arr[2], index).length);
+ }
+ arr[2] = index;
+ }
+ else if(index == patch_headers.length - 1 && arr[2] != undefined) {
+ arr[1].push(patch_headers.slice(arr[2], index).length);
+ }
+ return arr;
+ });
+ }, Promise.resolve([ [], [], undefined ]));
+
+ console.log(patch_header_data);
+
+ const patches = await diff.patches();
+ const parsed_patches = patches.reduce((acc, patch, patch_index) =>
+ {
+ return acc.then((arr) =>
+ {
+ return patch.hunks().then((hunks) =>
+ {
+ console.log("\n" + patch.newFile().path());
+
+ const patch_start = patch_header_data[0][patch_index] + patch_header_data[1][patch_index];
+ const patch_end = (patch_header_data[0][patch_index + 1] !== undefined) ? patch_header_data[0][patch_index + 1] : all_patches.length - 1;
+ const patch_content = all_patches.slice(patch_start, patch_end);
+
+ const line_lengths = patch_content.map((line) => line.length).reduce((acc, length) => acc + length);
+
+ if(patch_content.length > 5000 || line_lengths > 5000) {
+ console.log("Too large!");
+
+ arr.push({
+ from: patch.oldFile().path(),
+ to: patch.newFile().path(),
+ additions: patch.lineStats()["total_additions"],
+ deletions: patch.lineStats()["total_deletions"],
+ too_large: true,
+ hunks: null
+ });
+ return arr;
+ }
+
+ // Go through all of the patch's hunks
+ // Patches are split into parts of where in the file the change is made. Those parts are called hunks.
+ return hunks.reduce((acc, hunk, hunk_index) =>
+ {
+ return acc.then((hunks_data) =>
+ {
+ const hunk_header = hunk.header();
+ const hunk_header_index = patch_content.indexOf(hunk_header.replace(/\n/g, ""));
+
+ if(hunks_data[0] !== undefined) {
+ const prev_hunk = hunks[hunk_index - 1];
+ hunks_data[1].push(Object.assign({
+ new_start: prev_hunk.newStart(),
+ new_lines: prev_hunk.newLines(),
+ old_start: prev_hunk.oldStart(),
+ old_lines: prev_hunk.oldLines(),
+ }, parseHunkAddDel(patch_content.slice(hunks_data[0], hunk_header_index))));
+
+ hunks_data[2] = hunks_data + patch_content.slice(hunks_data[0], hunk_header_index).length;
+ }
+
+ hunks_data[0] = hunk_header_index;
+ return hunks_data;
+ });
+ }, Promise.resolve([ undefined, [], 0 ])).then((hunks_data) =>
+ {
+ const prev_hunk = hunks[hunks.length - 1];
+ hunks_data[1].push(Object.assign({
+ new_start: prev_hunk.newStart(),
+ new_lines: prev_hunk.newLines(),
+ old_start: prev_hunk.oldStart(),
+ old_lines: prev_hunk.oldLines(),
+ }, parseHunkAddDel(patch_content.slice(hunks_data[0], patch_end))));
+
+ arr.push({
+ from: patch.oldFile().path(),
+ to: patch.isDeleted() ? "/dev/null" : patch.newFile().path(),
+ additions: patch.lineStats()["total_additions"],
+ deletions: patch.lineStats()["total_deletions"],
+ too_large: false,
+ hunks: hunks_data[1]
+ });
+
+ return arr;
+ });
+ });
+ });
+ }, Promise.resolve([]));
+
+ return {
+ hash: commit.sha(),
+ author: commit.author().toString(),
+ message: commit.message(),
+ date: commit.date(),
+ patches: await parsed_patches
+ };
+}
+
+module.exports.getLog = getLog;
+module.exports.getRepos = getRepos;
+module.exports.getRepoFile = getRepoFile;
+module.exports.getCommit = getCommit; \ No newline at end of file
diff --git a/src/api/sanitization.js b/src/api/sanitization.js
new file mode 100644
index 0000000..95c8810
--- /dev/null
+++ b/src/api/sanitization.js
@@ -0,0 +1,14 @@
+function sanitizeRepoName(dirty)
+{
+ const valid_repo_name = /^[a-zA-Z0-9\.\-_]+$/;
+ return valid_repo_name.test(dirty);
+}
+
+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/src/api/v1.js b/src/api/v1.js
new file mode 100644
index 0000000..7920afc
--- /dev/null
+++ b/src/api/v1.js
@@ -0,0 +1,74 @@
+const express = require("express");
+const git = require("./git");
+const sanitization = require("./sanitization");
+
+const router = express.Router();
+
+router.get("/info", function(req, res)
+{
+ res.json({ "data": req.settings });
+ return;
+});
+
+router.get("/repos", async function(req, res)
+{
+ let repos = await git.getRepos(req.settings["base_dir"]);
+
+ if(repos["error"]) {
+ res.status(500).send("Internal server error!");
+ return;
+ }
+
+ res.json({ "data": repos });
+});
+
+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");
+
+ res.json({ "data": { "name": req.params.repo, "description": desc } });
+});
+
+router.get("/repos/:repo/log", async function(req, res)
+{
+ const repo = `${req.params.repo}.git`;
+ const log = await git.getLog(req.settings["base_dir"], repo);
+
+ if(log["error"]) {
+ if(typeof log["error"] === "string") {
+ res.status(500).json({ "error": log["error"] });
+ return;
+ }
+ switch(log["error"]) {
+ case 404:
+ res.status(404).json({ "error": "Git repository doesn't exist!" });
+ return;
+ }
+ return;
+ }
+ res.json({ data: 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({ data: commit });
+});
+
+module.exports = router; \ No newline at end of file
diff --git a/src/app.js b/src/app.js
index 1b43bbe..3c5ecd9 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,7 +1,96 @@
-import { createApp } from "vue/dist/vue.esm-bundler";
-import App from "./App.vue";
-import router from "./router";
+const express = require("express");
+const api = require("./api/v1");
+const yaml = require('js-yaml');
+const fs = require('fs');
+const { exit } = require("process");
-createApp(App)
- .use(router)
- .mount("#app"); \ No newline at end of file
+const settings = yaml.load(fs.readFileSync(__dirname + "/../settings.yml", 'utf8'));
+const settings_keys = Object.keys(settings);
+
+const mandatory_settings = [ "host", "port", "title", "about", "base_dir" ];
+
+const mandatory_not_included = settings_keys.filter(x => !mandatory_settings.includes(x));
+const settings_not_included = mandatory_settings.filter(x => !settings_keys.includes(x));
+
+if(settings_not_included.length !== 0) {
+ console.log(`Error: settings.yml is missing ${(mandatory_not_included.length > 1) ? "keys" : "key"}:`);
+ console.log(settings_not_included.join(", "));
+ exit(1);
+}
+if(mandatory_not_included.length !== 0) {
+ console.log(`Error: settings.yml includes ${(mandatory_not_included.length > 1) ? "pointless keys" : "a pointless key"}:`);
+ console.log(mandatory_not_included.join(", "));
+ exit(1);
+}
+
+const dist_dir = __dirname + "/../dist";
+
+const app = express();
+
+app.get(/.*\.(css|js|ico)$/, (req, res, next) =>
+{
+ fs.access(`${dist_dir}${req.path}`, err =>
+ {
+ if(err) {
+ next();
+ return;
+ }
+ res.sendFile(`${req.path}`, { root: dist_dir });
+ });
+});
+
+app.use("/api/v1", (req, res, next) =>
+{
+ req.settings = settings;
+ next();
+}, api);
+
+app.get("/", (req, res) =>
+{
+ res.sendFile(`app.html`, { root: dist_dir });
+});
+
+const repo_router = express.Router();
+
+app.use("/:repo([a-zA-Z0-9-_]+)", (req, res, next) =>
+{
+ console.log("AAAA");
+ fs.readdir(settings["base_dir"], (err, dir_content) =>
+ {
+ if(err) {
+ res.status(404).send("404: Not found");
+ return;
+ }
+
+ dir_content = dir_content.filter(repo => repo.endsWith(".git"));
+ if(!dir_content.includes(req.params.repo + ".git")) {
+ res.status(404).send("404: Not found");
+ return;
+ }
+ else {
+ next();
+ }
+ });
+}, repo_router);
+
+repo_router.get(/$|log$|refs$|tree$/, (req, res) =>
+{
+ res.sendFile(`app.html`, { root: dist_dir });
+});
+
+repo_router.get(/\/log\/[a-fA-F0-9]{40}$/, (req, res) =>
+{
+ res.sendFile(`app.html`, { root: dist_dir });
+})
+
+repo_router.use((req, res) =>
+{
+ res.status(404).send("404: Not found eeee");
+});
+
+app.use((req, res) =>
+{
+ res.status(404).send("404: Not found");
+})
+
+app.listen(settings["port"], settings["host"], () => console.log(`App is running on ${settings["host"]}:${settings["port"]}`)); \ No newline at end of file
diff --git a/src/App.vue b/src/frontend/App.vue
index cbdce56..cbdce56 100644
--- a/src/App.vue
+++ b/src/frontend/App.vue
diff --git a/src/app.html b/src/frontend/app.html
index 348ca45..348ca45 100644
--- a/src/app.html
+++ b/src/frontend/app.html
diff --git a/src/frontend/app.js b/src/frontend/app.js
new file mode 100644
index 0000000..1b43bbe
--- /dev/null
+++ b/src/frontend/app.js
@@ -0,0 +1,7 @@
+import { createApp } from "vue/dist/vue.esm-bundler";
+import App from "./App.vue";
+import router from "./router";
+
+createApp(App)
+ .use(router)
+ .mount("#app"); \ No newline at end of file
diff --git a/src/components/BaseBackButton.vue b/src/frontend/components/BaseBackButton.vue
index 64b1286..64b1286 100644
--- a/src/components/BaseBackButton.vue
+++ b/src/frontend/components/BaseBackButton.vue
diff --git a/src/components/CommitPatch.vue b/src/frontend/components/CommitPatch.vue
index 53edeb9..53edeb9 100644
--- a/src/components/CommitPatch.vue
+++ b/src/frontend/components/CommitPatch.vue
diff --git a/src/components/HomeHeader.vue b/src/frontend/components/HomeHeader.vue
index f0366a3..f0366a3 100644
--- a/src/components/HomeHeader.vue
+++ b/src/frontend/components/HomeHeader.vue
diff --git a/src/components/RepositoryHeader.vue b/src/frontend/components/RepositoryHeader.vue
index b0db4f9..b0db4f9 100644
--- a/src/components/RepositoryHeader.vue
+++ b/src/frontend/components/RepositoryHeader.vue
diff --git a/src/components/RepositoryNavbar.vue b/src/frontend/components/RepositoryNavbar.vue
index a1e1002..a1e1002 100644
--- a/src/components/RepositoryNavbar.vue
+++ b/src/frontend/components/RepositoryNavbar.vue
diff --git a/src/router/index.js b/src/frontend/router/index.js
index 68762cd..68762cd 100644
--- a/src/router/index.js
+++ b/src/frontend/router/index.js
diff --git a/src/scss/abstracts/_colors.scss b/src/frontend/scss/abstracts/_colors.scss
index d7c43f5..d7c43f5 100644
--- a/src/scss/abstracts/_colors.scss
+++ b/src/frontend/scss/abstracts/_colors.scss
diff --git a/src/scss/abstracts/_fonts.scss b/src/frontend/scss/abstracts/_fonts.scss
index 6af5233..6af5233 100644
--- a/src/scss/abstracts/_fonts.scss
+++ b/src/frontend/scss/abstracts/_fonts.scss
diff --git a/src/scss/style.scss b/src/frontend/scss/style.scss
index 89ea349..77fee90 100644
--- a/src/scss/style.scss
+++ b/src/frontend/scss/style.scss
@@ -1,9 +1,9 @@
@use "abstracts/colors";
@use "abstracts/fonts";
-@import "../../node_modules/bootstrap/scss/functions";
-@import "../../node_modules/bootstrap/scss/variables";
-@import "../../node_modules/bootstrap/scss/mixins";
+@import "../../../node_modules/bootstrap/scss/functions";
+@import "../../../node_modules/bootstrap/scss/variables";
+@import "../../../node_modules/bootstrap/scss/mixins";
$theme-colors: (
"primary": colors.$primary,
@@ -36,11 +36,11 @@ $font-sizes: (
$navbar-nav-link-padding-x: 0.5rem;
-@import "../../node_modules/bootstrap/scss/breadcrumb";
-@import "../../node_modules/bootstrap/scss/utilities";
-@import "../../node_modules/bootstrap/scss/utilities/api";
-@import "../../node_modules/bootstrap/scss/nav";
-@import "../../node_modules/bootstrap/scss/navbar";
+@import "../../../node_modules/bootstrap/scss/breadcrumb";
+@import "../../../node_modules/bootstrap/scss/utilities";
+@import "../../../node_modules/bootstrap/scss/utilities/api";
+@import "../../../node_modules/bootstrap/scss/nav";
+@import "../../../node_modules/bootstrap/scss/navbar";
$table-cell-padding-x: 1rem;
$table-cell-padding-y: 0.2rem;
@@ -56,12 +56,12 @@ $table-variants: (
"dark": colors.$background,
);
-@import "../../node_modules/bootstrap/scss/tables";
+@import "../../../node_modules/bootstrap/scss/tables";
-@import "../../node_modules/bootstrap/scss/containers";
-@import "../../node_modules/bootstrap/scss/grid";
+@import "../../../node_modules/bootstrap/scss/containers";
+@import "../../../node_modules/bootstrap/scss/grid";
-@import "../../node_modules/highlight.js/scss/srcery.scss";
+@import "../../../node_modules/highlight.js/scss/srcery.scss";
body {
background-color: colors.$background;
diff --git a/src/util/hljs-languages.js b/src/frontend/util/hljs-languages.js
index c8576e0..c8576e0 100644
--- a/src/util/hljs-languages.js
+++ b/src/frontend/util/hljs-languages.js
diff --git a/src/views/Home.vue b/src/frontend/views/Home.vue
index 79ec4ab..79ec4ab 100644
--- a/src/views/Home.vue
+++ b/src/frontend/views/Home.vue
diff --git a/src/views/Repository.vue b/src/frontend/views/Repository.vue
index 8863529..8863529 100644
--- a/src/views/Repository.vue
+++ b/src/frontend/views/Repository.vue
diff --git a/src/views/RepositoryCommit.vue b/src/frontend/views/RepositoryCommit.vue
index 283ed69..283ed69 100644
--- a/src/views/RepositoryCommit.vue
+++ b/src/frontend/views/RepositoryCommit.vue
diff --git a/src/views/RepositoryLog.vue b/src/frontend/views/RepositoryLog.vue
index 399fc78..399fc78 100644
--- a/src/views/RepositoryLog.vue
+++ b/src/frontend/views/RepositoryLog.vue