Skip to content
Source
Sign inSign up

gitLabChangeRequestTemplate.ts

53 lines1.8 KB

1import * as Effect from "effect/Effect";
2import * as Option from "effect/Option";
3​
4import type * as SourceControlHost from "@t3tools/source-control-core/server/SourceControlHost";
5​
6const TEMPLATE_DIRECTORY = ".gitlab/merge_request_templates";
7const TEMPLATE_MAX_BYTES = 8_000;
8const TREE_LIST_MAX_BYTES = 100_000;
9const TRUNCATION_MARKER = "[truncated]";
10​
11type ExecuteGit = SourceControlHost.SourceControlHost["Service"]["git"]["execute"];
12​
13export const detect = Effect.fn("gitLabChangeRequestTemplate.detect")(
14 function* (cwd: string, treeish: string, executeGit: ExecuteGit) {
15 // Read committed regular blobs so symlinks never reach the host filesystem.
16 const listing = yield* executeGit({
17 operation: "gitLabChangeRequestTemplate.listTemplates",
18 cwd,
19 args: ["ls-tree", "-z", "--full-tree", treeish, "--", `${TEMPLATE_DIRECTORY}/`],
20 maxOutputBytes: TREE_LIST_MAX_BYTES,
21 appendTruncationMarker: true,
22 });
23 if (listing.stdoutTruncated) {
24 return Option.none();
25 }
26​
27 const objectId = listing.stdout
28 .split("\0")
29 .map((record) => /^100(?:644|755) blob ([0-9a-f]{40,64})\t(.+)$/.exec(record))
30 .find((entry) => entry?.[2]?.toLowerCase() === `${TEMPLATE_DIRECTORY}/default.md`)?.[1];
31 if (!objectId) {
32 return Option.none();
33 }
34​
35 const blob = yield* executeGit({
36 operation: "gitLabChangeRequestTemplate.readTemplate",
37 cwd,
38 args: ["cat-file", "blob", objectId],
39 maxOutputBytes: TEMPLATE_MAX_BYTES,
40 appendTruncationMarker: true,
41 });
42 const template = blob.stdout.trim();
43 if (template.length === 0) {
44 return Option.none();
45 }
46 return Option.some(
47 blob.stdoutTruncated && !template.endsWith(TRUNCATION_MARKER)
48 ? `${template}\n\n${TRUNCATION_MARKER}`
49 : template,
50 );
51 },
52 Effect.orElseSucceed(() => Option.none()),
53);