Open History to see the last commit
1import { SourceControlProviderKind } from "@t3tools/contracts";
2import * as Schema from "effect/Schema";
3import * as Option from "effect/Option";
4import type { ChangeRequest } from "@t3tools/contracts";
5
6const Repository = Schema.Struct({
7 full_name: Schema.String,
8 owner: Schema.Struct({ login: Schema.String }),
9});
10const Branch = Schema.Struct({
11 ref: Schema.String,
12 label: Schema.optional(Schema.String),
13 sha: Schema.String,
14 repo: Schema.NullOr(Repository),
15});
16// Once a pull request's head branch is deleted, Forgejo reports `ref` as `refs/pull/<N>/head`
17// and keeps the branch name only in `label`. An AGit pull request (`flow` 1) has no branch, only a
18// `user/topic` label, and without `repo` (a deleted fork) the label could name any repository's
19// branch, so the ref stays in both cases.
20export function forgejoHeadBranch(pr: {
21 readonly flow?: number | undefined;
22 readonly head: {
23 readonly ref: string;
24 readonly label?: string | undefined;
25 readonly repo: unknown;
26 };
27}) {
28 const { ref, label, repo } = pr.head;
29 return ref.startsWith("refs/pull/") && label && repo !== null && pr.flow !== 1 ? label : ref;
30}
31export const ForgejoPullRequestSchema = Schema.Struct({
32 number: Schema.Int,
33 title: Schema.String,
34 html_url: Schema.String,
35 state: Schema.String,
36 merged: Schema.Boolean,
37 draft: Schema.optional(Schema.Boolean),
38 flow: Schema.optional(Schema.Int),
39 base: Branch,
40 head: Branch,
41 closed_at: Schema.optional(Schema.NullOr(Schema.String)),
42 merged_at: Schema.optional(Schema.NullOr(Schema.String)),
43 updated_at: Schema.optional(Schema.OptionFromNullOr(Schema.DateTimeUtcFromString)),
44});
45export function toForgejoChangeRequest(raw: typeof ForgejoPullRequestSchema.Type): ChangeRequest {
46 return {
47 provider: SourceControlProviderKind.make("forgejo"),
48 number: raw.number,
49 title: raw.title,
50 url: raw.html_url,
51 state: raw.merged ? "merged" : raw.state === "closed" ? "closed" : "open",
52 isDraft: raw.draft ?? /^(?:\[WIP\]|WIP:)/i.test(raw.title),
53 baseRefName: raw.base.ref,
54 headRefName: forgejoHeadBranch(raw),
55 ...(raw.head.sha.trim().length > 0 ? { headSha: raw.head.sha.trim() } : {}),
56 closedAt: raw.closed_at ?? null,
57 mergedAt: raw.merged_at ?? null,
58 updatedAt: raw.updated_at ?? Option.none(),
59 isCrossRepository:
60 raw.head.repo !== null &&
61 raw.base.repo !== null &&
62 raw.head.repo.full_name !== raw.base.repo.full_name,
63 headRepositoryNameWithOwner: raw.head.repo?.full_name ?? null,
64 headRepositoryOwnerLogin: raw.head.repo?.owner.login ?? null,
65 };
66}