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  | 
