aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/server/package.json2
-rw-r--r--packages/server/src/api/git.ts126
-rw-r--r--packages/server/src/api/v1.ts44
-rw-r--r--packages/server/src/app.ts9
4 files changed, 178 insertions, 3 deletions
diff --git a/packages/server/package.json b/packages/server/package.json
index 90e54ea..1a9f352 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -14,12 +14,14 @@
"js-yaml": "^4.1.0",
"nodegit": "^0.27.0",
"nodemon": "^2.0.7",
+ "tar-stream": "^2.2.0",
"whatwg-url": "^8.5.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.1",
"@types/node": "^15.12.1",
"@types/nodegit": "^0.27.2",
+ "@types/tar-stream": "^2.2.0",
"@types/whatwg-url": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
diff --git a/packages/server/src/api/git.ts b/packages/server/src/api/git.ts
index 880e5c0..97b0d88 100644
--- a/packages/server/src/api/git.ts
+++ b/packages/server/src/api/git.ts
@@ -1,4 +1,4 @@
-import { ConvenientHunk, ConvenientPatch, Repository, Revwalk, TreeEntry } from "nodegit";
+import { Commit, ConvenientHunk, ConvenientPatch, Object, Repository, Revwalk, Tag, TreeEntry } from "nodegit";
import { FastifyReply, FastifyRequest } from "fastify";
import { join, parse } from "path";
import { readFile, readdir } from "fs";
@@ -6,6 +6,9 @@ import { IncomingMessage } from "http";
import { URL } from "whatwg-url";
import { spawn } from "child_process";
import { verifyGitRequest } from "./util";
+import { pack } from "tar-stream";
+import { pipeline } from "stream";
+import { createGzip } from "zlib";
export declare namespace Git {
interface Hunk {
@@ -73,7 +76,7 @@ export declare namespace Git {
}
// eslint-disable-next-line no-unused-vars
- type Commit = {
+ type ShortCommit = {
id: string,
author: string,
message: string,
@@ -321,7 +324,7 @@ export class GitAPI {
}, Promise.resolve(<Git.ShortRepository[]>[]));
}
- async getCommit(repo_name: string, commit_oid: string): Promise<Git.Commit> {
+ async getCommit(repo_name: string, commit_oid: string): Promise<Git.ShortCommit> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
const commit = await repo.getCommit(commit_oid);
@@ -484,4 +487,121 @@ export class GitAPI {
return Boolean(readme);
}
+
+ async getBranches(repo_name: string) {
+ const full_repo_name = addRepoDirSuffix(repo_name);
+ const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
+
+ const references = await repo.getReferences();
+
+ return references.filter(ref => ref.isBranch()).map(ref => {
+ return {
+ id: ref.target().tostrS(),
+ name: ref.shorthand()
+ }
+ });
+ }
+
+ async getBranch(repo_name: string, branch_id: string) {
+ const full_repo_name = addRepoDirSuffix(repo_name);
+ const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
+
+ const references = await repo.getReferences();
+ const branches = references.filter(ref => ref.isBranch());
+
+ const branch = branches.find(_branch => _branch.target().tostrS() === branch_id);
+ if(!branch) {
+ return null;
+ }
+
+ const latest_commit = await repo.getBranchCommit(branch);
+
+ return {
+ name: branch.shorthand(),
+ latest_commit: {
+ id: latest_commit.sha(),
+ message: latest_commit.message(),
+ date: latest_commit.time()
+ }
+ };
+ }
+
+ async getTags(repo_name: string) {
+ const full_repo_name = addRepoDirSuffix(repo_name);
+ const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
+
+ const references = await repo.getReferences();
+
+ return Promise.all(references.filter(ref => ref.isTag()).map(async ref => {
+ const tagger = (await Tag.lookup(repo, ref.target())).tagger();
+
+ return {
+ name: ref.shorthand(),
+ author: {
+ name: tagger.name(),
+ email: tagger.email()
+ },
+ date: tagger.when().time()
+ };
+ }));
+ }
+
+ async downloadTagArchive(repo_name: string, tag_name: string, reply: FastifyReply): Promise<void> {
+ const full_repo_name = addRepoDirSuffix(repo_name);
+ const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
+
+ const reference = await repo.getReference(tag_name)
+ .catch(() => {
+ reply.code(404).send("Tag not found!");
+ return null;
+ });
+ if(!reference) {
+ return;
+ }
+
+ let tree;
+
+ try {
+ const commit = await Commit.lookup(repo, (await reference.peel(Object.TYPE.COMMIT)).id())
+ tree = await commit.getTree()
+ }
+ catch {
+ reply.code(500).send("Internal server error!");
+ return;
+ }
+
+ const archive = pack();
+ const gzip = createGzip();
+
+ reply.raw.writeHead(200, {
+ "Content-Encoding": "gzip",
+ "Content-Type": "application/gzip",
+ "Content-Disposition": `attachment; filename="${repo_name}-${tag_name}.tar.gz"`
+ });
+
+ pipeline(archive, gzip, reply.raw, () => reply.raw.end());
+
+ gzip.on("close", () => reply.raw.end());
+ gzip.on("error", () => reply.raw.end());
+ archive.on("error", () => reply.raw.end());
+
+ async function addArchiveEntries(entries: TreeEntry[]) {
+ for(const tree_entry of entries) {
+ if(tree_entry.isBlob()) {
+ const blob = await tree_entry.getBlob();
+ archive.entry({ name: `${repo_name}/${tree_entry.path()}` }, blob.content().toString());
+ }
+ else if(tree_entry.isTree()) {
+ await addArchiveEntries((await tree_entry.getTree()).entries());
+ }
+ }
+ }
+
+ addArchiveEntries(tree.entries())
+ .then(() => archive.finalize())
+ .catch(() => {
+ archive.finalize();
+ reply.raw.end();
+ });
+ }
}; \ No newline at end of file
diff --git a/packages/server/src/api/v1.ts b/packages/server/src/api/v1.ts
index e6391a0..b75c473 100644
--- a/packages/server/src/api/v1.ts
+++ b/packages/server/src/api/v1.ts
@@ -6,6 +6,13 @@ import { GitAPI } from "./git";
export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, done: any) {
const git = new GitAPI(opts.config.settings.base_dir);
+ fastify.setErrorHandler((err, req, reply) => {
+ reply.code(500).send({ error: "Internal server error!" });
+ });
+ fastify.setNotFoundHandler((req, reply) => {
+ reply.code(404).send({ error: "Endpoint not found!" });
+ })
+
fastify.route({
method: "GET",
url: "/info",
@@ -107,6 +114,43 @@ export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, do
}
});
+ fastify_repo.route({
+ method: "GET",
+ url: "/branches",
+ handler: async(req, reply) => {
+ const params: any = req.params;
+ const branches = await git.getBranches(params.repo);
+
+ reply.send({ data: branches });
+ }
+ });
+
+ fastify_repo.route({
+ method: "GET",
+ url: "/branches/:branch",
+ handler: async(req, reply) => {
+ const params: any = req.params;
+ const branch = await git.getBranch(params.repo, params.branch);
+
+ if(!branch) {
+ reply.code(404).send({ error: "Branch not found!" });
+ return;
+ }
+
+ reply.send({ data: branch });
+ }
+ });
+
+ fastify_repo.route({
+ method: "GET",
+ url: "/tags",
+ handler: async(req, reply) => {
+ const params: any = req.params;
+ const refs = await git.getTags(params.repo);
+ reply.send({ data: refs });
+ }
+ });
+
done_repo();
}, { prefix: "/repos/:repo" });
diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts
index d5f63b0..263703b 100644
--- a/packages/server/src/app.ts
+++ b/packages/server/src/app.ts
@@ -117,6 +117,15 @@ fastify.route({
}
});
+fastify.route({
+ method: "GET",
+ url: "/:repo([a-zA-Z0-9\\.\\-_]+)/refs/tags/:tag",
+ handler: (req, reply) => {
+ const params: any = req.params;
+ git.downloadTagArchive(params.repo, params.tag, reply);
+ }
+});
+
fastify.listen(settings.port, settings.host, (err: Error, addr: string) => {
if(err) {
console.error(err);