Open History to see the last commit
1import { SourceControlProviderKind } from "@t3tools/contracts";
2import {
3 buildCommitUrl,
4 changeRequestUrlBelow,
5 defineSourceControl,
6 isChangeRequestInProjectRepository,
7 isChangeRequestOnProjectHost,
8 parseChangeRequestPath,
9 parseCommitPath,
10 repositoryUrlFromPagePath,
11 projectChangeRequestHost,
12 remoteWebOrigin,
13} from "@t3tools/source-control-core/shared/definition";
14import { icon } from "./icon.ts";
15import { gitLabReferenceAutolinks } from "./referenceAutolinks.ts";
16
17const CHECKOUT_COMMAND = /^glab\s+mr\s+checkout\s+(.+)$/i;
18const CHANGE_REQUEST_REFERENCE =
19 /^https?:\/\/[^/?#\s]+\/[^/?#\s]+(?:\/[^/?#\s]+)+\/-\/merge_requests\/(\d+)(?:[/?#].*)?$/i;
20
21// Self-managed GitLab runs on any hostname and nests projects in groups.
22const CHANGE_REQUEST_PATH = /^\/([^/]+(?:\/[^/]+)+)\/-\/merge_requests\/(\d+)(?:\/|$)/u;
23const COMMIT_PATH = /^\/([^/]+(?:\/[^/]+)+)\/-\/commit\/([0-9a-f]+)\/?$/iu;
24// Every GitLab page inside a project sits below its first `/-/`.
25const REPOSITORY_PAGE_PATH = /^\/([^/]+(?:\/[^/]+)+?)\/-\//u;
26
27const KIND = SourceControlProviderKind.make("gitlab");
28
29export const definition = defineSourceControl({
30 kind: KIND,
31 label: "GitLab",
32 pickerLabel: "GitLab",
33 icon,
34 changeRequest: {
35 shortLabel: "MR",
36 shortLabelWithArticle: "an MR",
37 singular: "merge request",
38 plural: "merge requests",
39 },
40 repositoryPathHint: "group/project",
41 publicHost: "gitlab.com",
42 publishDescription: "gitlab.com",
43 publishHost: () => "gitlab.com",
44 newRepositoryOwner: (account) => ({ owner: account }),
45 defaultCloneTransport: "ssh",
46 changeRequestUrl: ({ host, repository, number, remoteUrl, webUrl }) =>
47 webUrl
48 ? changeRequestUrlBelow(webUrl, `-/merge_requests/${number}`)
49 : `${remoteWebOrigin(host, remoteUrl)}/${repository}/-/merge_requests/${number}`,
50 parseChangeRequestUrl: (url) => parseChangeRequestPath(url, CHANGE_REQUEST_PATH),
51 commitUrl: (input) => buildCommitUrl(input, "-/commit"),
52 parseCommitUrl: (url) => parseCommitPath(url, COMMIT_PATH),
53 repositoryUrlFromPage: (url) => repositoryUrlFromPagePath(url, REPOSITORY_PAGE_PATH),
54 // `!12` already links the merge request itself; `#12` is always an issue.
55 changeRequestUrlFromReferenceAutolink: (url) =>
56 CHANGE_REQUEST_PATH.test(url.pathname) ? url.toString() : null,
57 changeRequestActions: new Set([
58 "merge",
59 "ready",
60 "draft",
61 "close",
62 "reopen",
63 "update-branch",
64 "enable-auto-merge",
65 "disable-auto-merge",
66 "revert",
67 "delete-branch",
68 ] as const),
69 checkoutCommand: ({ number }) => `glab mr checkout ${number}`,
70 // `accept` is glab's alias for `merge`.
71 changeRequestCloseCommand: /\bglab\s+mr\s+(?:merge|accept|close)\b/u,
72 // `new` is glab's alias for `create`.
73 changeRequestCreateCommand: /\bglab\s+mr\s+(?:create|new)\b/u,
74 // Bot users, unlike GitHub's, have real profile pages.
75 authorProfileUrl: (login, repositoryUrl) =>
76 new URL(`/${encodeURIComponent(login)}`, repositoryUrl).toString(),
77 referenceAutolinks: gitLabReferenceAutolinks,
78 reviewSummaryRequired: () => false,
79 checkoutCommandArgument: (input) => CHECKOUT_COMMAND.exec(input)?.[1]?.trim() ?? null,
80 isChangeRequestReference: (url) => CHANGE_REQUEST_REFERENCE.test(url),
81 checkoutChangeRequestHost: () => null,
82 changeRequestHeadRef: (number) => `refs/merge-requests/${number}/head`,
83 recognizeRemote: ({ host, hostname }) =>
84 hostname === "gitlab.com" || hostname.split(".").includes("gitlab")
85 ? {
86 kind: KIND,
87 name: hostname === "gitlab.com" ? "GitLab" : "GitLab Self-Hosted",
88 baseUrl: `https://${host}`,
89 }
90 : null,
91 isChangeRequestInRepository: (identity, link) =>
92 isChangeRequestInProjectRepository(projectChangeRequestHost(identity, KIND), identity, link),
93 canReadChangeRequestOnHost: (identity, link) =>
94 isChangeRequestOnProjectHost(projectChangeRequestHost(identity, KIND), identity, link),
95});