92 lines
3.4 KiB
JavaScript
92 lines
3.4 KiB
JavaScript
/**
|
|
* @file The Changes panel's history reads, mounted on the lanes router
|
|
* (`/api/lanes/:id/git/...` and its `/api/workspaces` alias): commit details,
|
|
* a commit's patch, a file's history across renames, blame at a revision, a
|
|
* tree at a revision, a two-ref comparison, a cherry-pick/revert plan and a
|
|
* paused one's state. All of it is server/lib/git-history.js.
|
|
*
|
|
* Reads, so unguarded like every other `/git/*` read — and each resolves its
|
|
* repository exactly like them, through the lanes router's `gitTargetOr404`
|
|
* (the lane row, `?session=`, `?source=`, `?repo=`), never a path from the
|
|
* request. Error mapping matches `/git/file`: a secret or a foreign folder is
|
|
* 403, a bad request 400, anything else (not a repo, no commits) a 200
|
|
* `{available: false}` — a state, not a fault.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const history = require("../lib/git-history");
|
|
|
|
const BAD = new Set([
|
|
"EBADPATH",
|
|
"EBADREV",
|
|
"EBADQUERY",
|
|
"EBADSHA",
|
|
"EBADACTION",
|
|
"ENOFILE",
|
|
"ETOOBIG",
|
|
"ENOBRANCH",
|
|
"EMERGECOMMIT",
|
|
]);
|
|
|
|
const str = (v) => (typeof v === "string" ? v : "");
|
|
|
|
/**
|
|
* @param {import("express").Router} router The lanes router.
|
|
* @param {Function} gitTargetOr404 Its repo resolver: `{lane, cwd}` or null
|
|
* (the 404 already sent).
|
|
*/
|
|
function registerGitHistory(router, gitTargetOr404) {
|
|
const read = (sub, fn) =>
|
|
router.get(`/:id/git/${sub}`, async (req, res) => {
|
|
const t = await gitTargetOr404(req, res);
|
|
if (!t) return;
|
|
try {
|
|
res.json({ available: true, ...(await fn(t.cwd, req.query)) });
|
|
} catch (err) {
|
|
if (err.code === "ESECRET" || err.code === "EFOREIGN") {
|
|
return res.status(403).json({ error: { code: err.code, message: err.message } });
|
|
}
|
|
if (BAD.has(err.code)) {
|
|
return res.status(400).json({ error: { code: err.code, message: err.message } });
|
|
}
|
|
res.json({ available: false });
|
|
}
|
|
});
|
|
|
|
/** GET /git/commit?sha= — one commit's message, people, parents, refs. */
|
|
read("commit", (cwd, q) => history.commitDetails(cwd, str(q.sha)));
|
|
/** GET /git/patch?sha= — the commit as `format-patch` text (Copy patch). */
|
|
read("patch", (cwd, q) => history.commitPatch(cwd, str(q.sha)));
|
|
/** GET /git/file-history?path=&skip=&limit= — `log --follow`. */
|
|
read("file-history", (cwd, q) => history.fileHistory(cwd, str(q.path), q));
|
|
/** GET /git/blame-at?path=&rev= — blame at a commit (or `work`). */
|
|
read("blame-at", (cwd, q) => history.blameAt(cwd, str(q.path), str(q.rev) || "work"));
|
|
/** GET /git/tree-at?rev=&path= — one folder of the tree at a commit. */
|
|
read("tree-at", (cwd, q) => history.treeAt(cwd, str(q.rev), str(q.path)));
|
|
/** GET /git/compare?a=&b=&mode=three|two[&path=&from=] — two refs. */
|
|
read("compare", (cwd, q) =>
|
|
history.compareRefs(cwd, {
|
|
a: str(q.a),
|
|
b: str(q.b),
|
|
mode: q.mode === "two" ? "two" : "three",
|
|
path: str(q.path) || null,
|
|
from: str(q.from) || null,
|
|
})
|
|
);
|
|
/** GET /git/pick-plan?op=cherry-pick|revert&shas=a,b — what it would run. */
|
|
read("pick-plan", (cwd, q) =>
|
|
history.pickPreview(
|
|
cwd,
|
|
str(q.op),
|
|
str(q.shas)
|
|
.split(",")
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)
|
|
)
|
|
);
|
|
/** GET /git/sequence — the cherry-pick/revert that is paused, if any. */
|
|
read("sequence", (cwd) => history.sequenceState(cwd));
|
|
}
|
|
|
|
module.exports = { registerGitHistory };
|