1/** Authorization and commit stay in the request effect, across every write transport. */
2import * as Git from "alchemy/Git";
3import * as Effect from "effect/Effect";
4import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
5import { AppApi } from "./api.ts";
6import { checkRefChanges } from "./branch-policy.ts";
7
8export const RefsLive = HttpApiBuilder.group(AppApi, "refs", (h) =>
9 Effect.gen(function* () {
10 const git = yield* Git.Engine;
11 const defaults = yield* Git.Handlers;
12 return h.handleAll({
13 ...defaults.refs,
14 update: ({ params, query, payload }) =>
15 Effect.scoped(
16 Effect.gen(function* () {
17 if (/^0+$/.test(payload.newOid))
18 return yield* new Git.PushDenied({
19 ref: query.name,
20 reason: "use the DELETE endpoint to remove a ref",
21 });
22 const repo = yield* git.repositories.get(params);
23 const prepared = yield* git.prepareRefUpdate(repo, {
24 ref: query.name,
25 ...payload,
26 });
27 yield* checkRefChanges(repo, prepared.updates);
28 const result = yield* prepared.commit;
29 if (result === undefined)
30 return yield* new Git.PushDenied({
31 ref: query.name,
32 reason: "use the DELETE endpoint to remove a ref",
33 });
34 return new Git.Ref({
35 name: result.name,
36 oid: result.oid as Git.Oid,
37 });
38 }),
39 ).pipe(Effect.catchTag("StoreError", Effect.die)),
40 remove: ({ params, query, payload }) =>
41 Effect.scoped(
42 Effect.gen(function* () {
43 const repo = yield* git.repositories.get(params);
44 const prepared = yield* git.prepareRefRemoval(repo, {
45 ref: query.name,
46 ...payload,
47 });
48 yield* checkRefChanges(repo, prepared.updates);
49 yield* prepared.commit;
50 }),
51 ).pipe(Effect.catchTag("StoreError", Effect.die)),
52 });
53 }),
54);
55
56/** Used by both the typed REST endpoint and the GitHub facade. */
57export const mergePull = (
58 git: Effect.Success<typeof Git.Engine>,
59 params: { owner: string; repo: string; number: number },
60 input: { message?: string; expectedHeadOid?: string },
61) =>
62 Effect.scoped(
63 Effect.gen(function* () {
64 const repo = yield* git.repositories.get(params);
65 const prepared = yield* git.prepareMerge(repo, {
66 number: params.number,
67 ...input,
68 });
69 yield* checkRefChanges(repo, prepared.updates);
70 return yield* prepared.commit;
71 }),
72 ).pipe(Effect.catchTag("StoreError", Effect.die));
73
74export const PullsLive = HttpApiBuilder.group(AppApi, "pulls", (h) =>
75 Effect.gen(function* () {
76 const defaults = yield* Git.Handlers;
77 const git = yield* Git.Engine;
78 return h.handleAll({
79 ...defaults.pulls,
80 merge: ({ params, payload }) =>
81 mergePull(git, params, payload).pipe(
82 Effect.map(
83 (result) =>
84 new Git.MergeResult({
85 ...result,
86 oid: result.oid as Git.Oid,
87 pull: new Git.Pull({
88 ...result.pull,
89 mergeCommit: result.pull.mergeCommit as Git.Oid | null,
90 }),
91 }),
92 ),
93 ),
94 });
95 }),
96);