| 1 | /** Authorization and commit stay in the request effect, across every write transport. */ |
| 2 | import * as Git from "alchemy/Git"; |
| 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder"; |
| 5 | import { AppApi } from "./api.ts"; |
| 6 | import { checkRefChanges } from "./branch-policy.ts"; |
| 7 | |
| 8 | export 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. */ |
| 57 | export 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 | |
| 74 | export 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 | ); |