diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/git.js | 237 | ||||
-rw-r--r-- | src/api/sanitization.js | 14 | ||||
-rw-r--r-- | src/api/v1.js | 74 | ||||
-rw-r--r-- | src/app.js | 101 | ||||
-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.js | 7 | ||||
-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 @@ -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 |