mirror of
https://code.forgejo.org/actions/git-backporting.git
synced 2025-06-05 12:54:29 -04:00
feat: integrate tool with gitlab service (#39)
* feat: integrate tool with gitlab service Fix https://github.com/lampajr/backporting/issues/30
This commit is contained in:
parent
8a007941d1
commit
107f5e52d6
35 changed files with 17821 additions and 1553 deletions
56
src/service/git/git-client-factory.ts
Normal file
56
src/service/git/git-client-factory.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import GitClient from "@bp/service/git/git-client";
|
||||
import { GitClientType } from "@bp/service/git/git.types";
|
||||
import GitHubService from "@bp/service/git/github/github-client";
|
||||
import LoggerService from "@bp/service/logger/logger-service";
|
||||
import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
|
||||
import GitLabClient from "./gitlab/gitlab-client";
|
||||
|
||||
/**
|
||||
* Singleton git service factory class
|
||||
*/
|
||||
export default class GitClientFactory {
|
||||
|
||||
private static logger: LoggerService = LoggerServiceFactory.getLogger();
|
||||
private static instance?: GitClient;
|
||||
|
||||
public static getClient(): GitClient {
|
||||
if (!GitClientFactory.instance) {
|
||||
throw new Error("You must call `getOrCreate` method first!");
|
||||
}
|
||||
|
||||
return GitClientFactory.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the singleton git management service
|
||||
* @param type git management service type
|
||||
* @param authToken authentication token, like github/gitlab token
|
||||
*/
|
||||
public static getOrCreate(type: GitClientType, authToken: string, apiUrl: string): GitClient {
|
||||
|
||||
if (GitClientFactory.instance) {
|
||||
GitClientFactory.logger.warn("Git service already initialized!");
|
||||
return GitClientFactory.instance;
|
||||
}
|
||||
|
||||
this.logger.debug(`Setting up ${type} client: apiUrl=${apiUrl}, token=****`);
|
||||
|
||||
switch(type) {
|
||||
case GitClientType.GITHUB:
|
||||
GitClientFactory.instance = new GitHubService(authToken, apiUrl);
|
||||
break;
|
||||
case GitClientType.GITLAB:
|
||||
GitClientFactory.instance = new GitLabClient(authToken, apiUrl);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid git service type received: ${type}`);
|
||||
}
|
||||
|
||||
return GitClientFactory.instance;
|
||||
}
|
||||
|
||||
public static reset(): void {
|
||||
GitClientFactory.logger.warn("Resetting git service!");
|
||||
GitClientFactory.instance = undefined;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import { BackportPullRequest, GitPullRequest } from "@bp/service/git/git.types";
|
|||
* Git management service interface, which provides a common API for interacting
|
||||
* with several git management services like GitHub, Gitlab or Bitbucket.
|
||||
*/
|
||||
export default interface GitService {
|
||||
export default interface GitClient {
|
||||
|
||||
// READ
|
||||
|
||||
|
@ -29,6 +29,7 @@ import { BackportPullRequest, GitPullRequest } from "@bp/service/git/git.types";
|
|||
/**
|
||||
* Create a new pull request on the underneath git service
|
||||
* @param backport backport pull request data
|
||||
* @returns {Promise<string>} the pull request url
|
||||
*/
|
||||
createPullRequest(backport: BackportPullRequest): Promise<void>;
|
||||
createPullRequest(backport: BackportPullRequest): Promise<string>;
|
||||
}
|
20
src/service/git/git-mapper.ts
Normal file
20
src/service/git/git-mapper.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { GitPullRequest, GitRepoState, GitRepository } from "@bp/service/git/git.types";
|
||||
|
||||
/**
|
||||
* Generic git client response mapper
|
||||
*
|
||||
* PR - full pull request schema type
|
||||
* S - pull request state type
|
||||
*/
|
||||
export default interface GitResponseMapper<PR, S> {
|
||||
|
||||
mapPullRequest(
|
||||
pr: PR,
|
||||
): Promise<GitPullRequest>;
|
||||
|
||||
mapGitState(state: S): GitRepoState;
|
||||
|
||||
mapSourceRepo(pull: PR): Promise<GitRepository>;
|
||||
|
||||
mapTargetRepo (pull: PR): Promise<GitRepository>;
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
import GitService from "@bp/service/git/git-service";
|
||||
import { GitServiceType } from "@bp/service/git/git.types";
|
||||
import GitHubService from "@bp/service/git/github/github-service";
|
||||
import LoggerService from "@bp/service/logger/logger-service";
|
||||
import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
|
||||
|
||||
/**
|
||||
* Singleton git service factory class
|
||||
*/
|
||||
export default class GitServiceFactory {
|
||||
|
||||
private static logger: LoggerService = LoggerServiceFactory.getLogger();
|
||||
private static instance?: GitService;
|
||||
|
||||
public static getService(): GitService {
|
||||
if (!GitServiceFactory.instance) {
|
||||
throw new Error("You must call `init` method first!");
|
||||
}
|
||||
|
||||
return GitServiceFactory.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the singleton git management service
|
||||
* @param type git management service type
|
||||
* @param auth authentication, like github token
|
||||
*/
|
||||
public static getOrCreate(type: GitServiceType, auth: string): void {
|
||||
|
||||
if (GitServiceFactory.instance) {
|
||||
GitServiceFactory.logger.warn("Git service already initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case GitServiceType.GITHUB:
|
||||
GitServiceFactory.instance = new GitHubService(auth);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid git service type received: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
39
src/service/git/git-util.ts
Normal file
39
src/service/git/git-util.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { GitClientType } from "@bp/service/git/git.types";
|
||||
|
||||
const PUBLIC_GITHUB_URL = "https://github.com";
|
||||
const PUBLIC_GITHUB_API = "https://api.github.com";
|
||||
|
||||
/**
|
||||
* Infer the remote GIT service to interact with based on the provided
|
||||
* pull request URL
|
||||
* @param prUrl provided pull request URL
|
||||
* @returns {GitClientType}
|
||||
*/
|
||||
export const inferGitClient = (prUrl: string): GitClientType => {
|
||||
const stdPrUrl = prUrl.toLowerCase().trim();
|
||||
|
||||
if (stdPrUrl.includes(GitClientType.GITHUB.toString())) {
|
||||
return GitClientType.GITHUB;
|
||||
} else if (stdPrUrl.includes(GitClientType.GITLAB.toString())) {
|
||||
return GitClientType.GITLAB;
|
||||
}
|
||||
|
||||
throw new Error(`Remote git service not recognized from pr url: ${prUrl}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Infer the host git service from the pull request url
|
||||
* @param prUrl pull/merge request url
|
||||
* @param apiVersion the api version, ignored in case of public github
|
||||
* @returns api URL like https://api.github.com or https://gitlab.com/api/v4
|
||||
*/
|
||||
export const inferGitApiUrl = (prUrl: string, apiVersion = "v4"): string => {
|
||||
const url = new URL(prUrl);
|
||||
const baseUrl = `${url.protocol}//${url.host}`;
|
||||
|
||||
if (baseUrl.includes(PUBLIC_GITHUB_URL)) {
|
||||
return PUBLIC_GITHUB_API;
|
||||
}
|
||||
|
||||
return `${baseUrl}/api/${apiVersion}`;
|
||||
};
|
|
@ -3,7 +3,7 @@ export interface GitPullRequest {
|
|||
author: string,
|
||||
url?: string,
|
||||
htmlUrl?: string,
|
||||
state?: "open" | "closed",
|
||||
state?: GitRepoState,
|
||||
merged?: boolean,
|
||||
mergedBy?: string,
|
||||
title: string,
|
||||
|
@ -35,6 +35,14 @@ export interface BackportPullRequest {
|
|||
branchName?: string,
|
||||
}
|
||||
|
||||
export enum GitServiceType {
|
||||
GITHUB = "github"
|
||||
export enum GitClientType {
|
||||
GITHUB = "github",
|
||||
GITLAB = "gitlab",
|
||||
}
|
||||
|
||||
export enum GitRepoState {
|
||||
OPEN = "open",
|
||||
CLOSED = "closed",
|
||||
LOCKED = "locked", // just on gitlab
|
||||
MERGED = "merged", // just on gitlab
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import GitService from "@bp/service/git/git-service";
|
||||
import GitClient from "@bp/service/git/git-client";
|
||||
import { BackportPullRequest, GitPullRequest } from "@bp/service/git/git.types";
|
||||
import GitHubMapper from "@bp/service/git/github/github-mapper";
|
||||
import OctokitFactory from "@bp/service/git/github/octokit-factory";
|
||||
|
@ -7,15 +7,17 @@ import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
|
|||
import { Octokit } from "@octokit/rest";
|
||||
import { PullRequest } from "@octokit/webhooks-types";
|
||||
|
||||
export default class GitHubService implements GitService {
|
||||
export default class GitHubClient implements GitClient {
|
||||
|
||||
private logger: LoggerService;
|
||||
private apiUrl: string;
|
||||
private octokit: Octokit;
|
||||
private mapper: GitHubMapper;
|
||||
|
||||
constructor(token: string) {
|
||||
constructor(token: string, apiUrl: string) {
|
||||
this.apiUrl = apiUrl;
|
||||
this.logger = LoggerServiceFactory.getLogger();
|
||||
this.octokit = OctokitFactory.getOctokit(token);
|
||||
this.octokit = OctokitFactory.getOctokit(token, this.apiUrl);
|
||||
this.mapper = new GitHubMapper();
|
||||
}
|
||||
|
||||
|
@ -33,13 +35,13 @@ export default class GitHubService implements GitService {
|
|||
}
|
||||
|
||||
async getPullRequestFromUrl(prUrl: string): Promise<GitPullRequest> {
|
||||
const {owner, project} = this.getRepositoryFromPrUrl(prUrl);
|
||||
return this.getPullRequest(owner, project, parseInt(prUrl.substring(prUrl.lastIndexOf("/") + 1, prUrl.length)));
|
||||
const { owner, project, id } = this.extractPullRequestData(prUrl);
|
||||
return this.getPullRequest(owner, project, id);
|
||||
}
|
||||
|
||||
// WRITE
|
||||
|
||||
async createPullRequest(backport: BackportPullRequest): Promise<void> {
|
||||
async createPullRequest(backport: BackportPullRequest): Promise<string> {
|
||||
this.logger.info(`Creating pull request ${backport.head} -> ${backport.base}.`);
|
||||
this.logger.info(`${JSON.stringify(backport, null, 2)}`);
|
||||
|
||||
|
@ -52,6 +54,10 @@ export default class GitHubService implements GitService {
|
|||
body: backport.body
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
throw new Error("Pull request creation failed");
|
||||
}
|
||||
|
||||
if (backport.reviewers.length > 0) {
|
||||
try {
|
||||
await this.octokit.pulls.requestReviewers({
|
||||
|
@ -77,6 +83,8 @@ export default class GitHubService implements GitService {
|
|||
this.logger.error(`Error setting assignees: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return data.html_url;
|
||||
}
|
||||
|
||||
// UTILS
|
||||
|
@ -86,11 +94,12 @@ export default class GitHubService implements GitService {
|
|||
* @param prUrl pull request url
|
||||
* @returns {{owner: string, project: string}}
|
||||
*/
|
||||
private getRepositoryFromPrUrl(prUrl: string): {owner: string, project: string} {
|
||||
private extractPullRequestData(prUrl: string): {owner: string, project: string, id: number} {
|
||||
const elems: string[] = prUrl.split("/");
|
||||
return {
|
||||
owner: elems[elems.length - 4],
|
||||
project: elems[elems.length - 3]
|
||||
project: elems[elems.length - 3],
|
||||
id: parseInt(prUrl.substring(prUrl.lastIndexOf("/") + 1, prUrl.length)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,19 @@
|
|||
import { GitPullRequest } from "@bp/service/git/git.types";
|
||||
import { GitPullRequest, GitRepoState, GitRepository } from "@bp/service/git/git.types";
|
||||
import { PullRequest, User } from "@octokit/webhooks-types";
|
||||
import GitResponseMapper from "@bp/service/git/git-mapper";
|
||||
|
||||
export default class GitHubMapper {
|
||||
export default class GitHubMapper implements GitResponseMapper<PullRequest, "open" | "closed"> {
|
||||
|
||||
mapPullRequest(pr: PullRequest): GitPullRequest {
|
||||
mapGitState(state: "open" | "closed"): GitRepoState {
|
||||
switch (state) {
|
||||
case "open":
|
||||
return GitRepoState.OPEN;
|
||||
default:
|
||||
return GitRepoState.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
async mapPullRequest(pr: PullRequest): Promise<GitPullRequest> {
|
||||
return {
|
||||
number: pr.number,
|
||||
author: pr.user.login,
|
||||
|
@ -11,24 +21,32 @@ export default class GitHubMapper {
|
|||
htmlUrl: pr.html_url,
|
||||
title: pr.title,
|
||||
body: pr.body ?? "",
|
||||
state: pr.state,
|
||||
state: this.mapGitState(pr.state), // TODO fix using custom mapper
|
||||
merged: pr.merged ?? false,
|
||||
mergedBy: pr.merged_by?.login,
|
||||
reviewers: pr.requested_reviewers.filter(r => "login" in r).map((r => (r as User)?.login)),
|
||||
assignees: pr.assignees.filter(r => "login" in r).map(r => r.login),
|
||||
sourceRepo: {
|
||||
owner: pr.head.repo.full_name.split("/")[0],
|
||||
project: pr.head.repo.full_name.split("/")[1],
|
||||
cloneUrl: pr.head.repo.clone_url
|
||||
},
|
||||
targetRepo: {
|
||||
owner: pr.base.repo.full_name.split("/")[0],
|
||||
project: pr.base.repo.full_name.split("/")[1],
|
||||
cloneUrl: pr.base.repo.clone_url
|
||||
},
|
||||
sourceRepo: await this.mapSourceRepo(pr),
|
||||
targetRepo: await this.mapTargetRepo(pr),
|
||||
nCommits: pr.commits,
|
||||
// if pr is open use latest commit sha otherwise use merge_commit_sha
|
||||
commits: pr.state === "open" ? [pr.head.sha] : [pr.merge_commit_sha as string]
|
||||
};
|
||||
}
|
||||
|
||||
async mapSourceRepo(pr: PullRequest): Promise<GitRepository> {
|
||||
return Promise.resolve({
|
||||
owner: pr.head.repo.full_name.split("/")[0],
|
||||
project: pr.head.repo.full_name.split("/")[1],
|
||||
cloneUrl: pr.head.repo.clone_url
|
||||
});
|
||||
}
|
||||
|
||||
async mapTargetRepo(pr: PullRequest): Promise<GitRepository> {
|
||||
return Promise.resolve({
|
||||
owner: pr.base.repo.full_name.split("/")[0],
|
||||
project: pr.base.repo.full_name.split("/")[1],
|
||||
cloneUrl: pr.base.repo.clone_url
|
||||
});
|
||||
}
|
||||
}
|
|
@ -10,12 +10,13 @@ export default class OctokitFactory {
|
|||
private static logger: LoggerService = LoggerServiceFactory.getLogger();
|
||||
private static octokit?: Octokit;
|
||||
|
||||
public static getOctokit(token: string): Octokit {
|
||||
public static getOctokit(token: string, apiUrl: string): Octokit {
|
||||
if (!OctokitFactory.octokit) {
|
||||
OctokitFactory.logger.info("Creating octokit instance.");
|
||||
OctokitFactory.octokit = new Octokit({
|
||||
auth: token,
|
||||
userAgent: "lampajr/backporting"
|
||||
userAgent: "lampajr/backporting",
|
||||
baseUrl: apiUrl
|
||||
});
|
||||
}
|
||||
|
||||
|
|
157
src/service/git/gitlab/gitlab-client.ts
Normal file
157
src/service/git/gitlab/gitlab-client.ts
Normal file
|
@ -0,0 +1,157 @@
|
|||
import LoggerService from "@bp/service/logger/logger-service";
|
||||
import GitClient from "@bp/service/git/git-client";
|
||||
import { GitPullRequest, BackportPullRequest } from "@bp/service/git/git.types";
|
||||
import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
|
||||
import { MergeRequestSchema, UserSchema } from "@gitbeaker/rest";
|
||||
import GitLabMapper from "@bp/service/git/gitlab/gitlab-mapper";
|
||||
import axios, { Axios } from "axios";
|
||||
import https from "https";
|
||||
|
||||
export default class GitLabClient implements GitClient {
|
||||
|
||||
private readonly logger: LoggerService;
|
||||
private readonly apiUrl: string;
|
||||
private readonly mapper: GitLabMapper;
|
||||
private readonly client: Axios;
|
||||
|
||||
constructor(token: string, apiUrl: string, rejectUnauthorized = false) {
|
||||
this.logger = LoggerServiceFactory.getLogger();
|
||||
this.apiUrl = apiUrl;
|
||||
this.client = axios.create({
|
||||
baseURL: this.apiUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"User-Agent": "lampajr/backporting",
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized
|
||||
})
|
||||
});
|
||||
this.mapper = new GitLabMapper(this.client);
|
||||
}
|
||||
|
||||
// READ
|
||||
|
||||
// example: <host>/api/v4/projects/alampare%2Fbackporting-example/merge_requests/1
|
||||
async getPullRequest(namespace: string, repo: string, mrNumber: number): Promise<GitPullRequest> {
|
||||
const projectId = this.getProjectId(namespace, repo);
|
||||
const { data } = await this.client.get(`/projects/${projectId}/merge_requests/${mrNumber}`);
|
||||
|
||||
return this.mapper.mapPullRequest(data as MergeRequestSchema);
|
||||
}
|
||||
|
||||
getPullRequestFromUrl(mrUrl: string): Promise<GitPullRequest> {
|
||||
const { namespace, project, id } = this.extractMergeRequestData(mrUrl);
|
||||
return this.getPullRequest(namespace, project, id);
|
||||
}
|
||||
|
||||
// WRITE
|
||||
|
||||
async createPullRequest(backport: BackportPullRequest): Promise<string> {
|
||||
this.logger.info(`Creating pull request ${backport.head} -> ${backport.base}.`);
|
||||
this.logger.info(`${JSON.stringify(backport, null, 2)}`);
|
||||
|
||||
const projectId = this.getProjectId(backport.owner, backport.repo);
|
||||
|
||||
const { data } = await this.client.post(`/projects/${projectId}/merge_requests`, {
|
||||
source_branch: backport.head,
|
||||
target_branch: backport.base,
|
||||
title: backport.title,
|
||||
description: backport.body,
|
||||
reviewer_ids: [],
|
||||
assignee_ids: [],
|
||||
});
|
||||
|
||||
const mr = data as MergeRequestSchema;
|
||||
|
||||
// reviewers
|
||||
const reviewerIds: number[] = [];
|
||||
for(const r of backport.reviewers) {
|
||||
try {
|
||||
this.logger.debug("Retrieving user: " + r);
|
||||
const user = await this.getUser(r);
|
||||
reviewerIds.push(user.id);
|
||||
} catch(error) {
|
||||
this.logger.warn(`Failed to retrieve reviewer ${r}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (reviewerIds.length > 0) {
|
||||
try {
|
||||
this.logger.info("Setting reviewers: " + reviewerIds);
|
||||
await this.client.put(`/projects/${projectId}/merge_requests/${mr.iid}`, {
|
||||
reviewer_ids: reviewerIds.filter(r => r !== undefined),
|
||||
});
|
||||
} catch(error) {
|
||||
this.logger.warn("Failure trying to update reviewers. " + error);
|
||||
}
|
||||
}
|
||||
|
||||
// assignees
|
||||
const assigneeIds: number[] = [];
|
||||
for(const a of backport.assignees) {
|
||||
try {
|
||||
this.logger.debug("Retrieving user: " + a);
|
||||
const user = await this.getUser(a);
|
||||
assigneeIds.push(user.id);
|
||||
} catch(error) {
|
||||
this.logger.warn(`Failed to retrieve assignee ${a}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (assigneeIds.length > 0) {
|
||||
try {
|
||||
this.logger.info("Setting assignees: " + assigneeIds);
|
||||
await this.client.put(`/projects/${projectId}/merge_requests/${mr.iid}`, {
|
||||
assignee_ids: assigneeIds.filter(a => a !== undefined),
|
||||
});
|
||||
} catch(error) {
|
||||
this.logger.warn("Failure trying to update assignees. " + error);
|
||||
}
|
||||
}
|
||||
|
||||
return mr.web_url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a gitlab user given its username
|
||||
* @param username
|
||||
* @returns UserSchema
|
||||
*/
|
||||
private async getUser(username: string): Promise<UserSchema> {
|
||||
const { data } = await this.client.get(`/users?username=${username}`);
|
||||
const users = data as UserSchema[];
|
||||
|
||||
if (users.length > 1) {
|
||||
throw new Error("Too many users found with username=" + username);
|
||||
}
|
||||
|
||||
if (users.length == 0) {
|
||||
throw new Error("User " + username + " not found");
|
||||
}
|
||||
|
||||
return users[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract repository namespace, project and mr number from the merge request url
|
||||
* example: <host>/alampare/backporting-example/-/merge_requests/1
|
||||
* note: "-/" could be omitted
|
||||
* @param mrUrl merge request url
|
||||
* @returns {{owner: string, project: string}}
|
||||
*/
|
||||
private extractMergeRequestData(mrUrl: string): {namespace: string, project: string, id: number} {
|
||||
const elems: string[] = mrUrl.replace("/-/", "/").split("/");
|
||||
return {
|
||||
namespace: elems[elems.length - 4],
|
||||
project: elems[elems.length - 3],
|
||||
id: parseInt(mrUrl.substring(mrUrl.lastIndexOf("/") + 1, mrUrl.length)),
|
||||
};
|
||||
}
|
||||
|
||||
private getProjectId(namespace: string, repo: string) {
|
||||
// e.g., <namespace>%2F<repo>
|
||||
return encodeURIComponent(`${namespace}/${repo}`);
|
||||
}
|
||||
}
|
83
src/service/git/gitlab/gitlab-mapper.ts
Normal file
83
src/service/git/gitlab/gitlab-mapper.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { GitPullRequest, GitRepoState, GitRepository } from "@bp/service/git/git.types";
|
||||
import GitResponseMapper from "@bp/service/git/git-mapper";
|
||||
import { MergeRequestSchema, ProjectSchema } from "@gitbeaker/rest";
|
||||
import { Axios } from "axios";
|
||||
|
||||
export default class GitLabMapper implements GitResponseMapper<MergeRequestSchema, string> {
|
||||
|
||||
private readonly client;
|
||||
// needs client to perform additional requests
|
||||
constructor(client: Axios) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
mapGitState(state: string): GitRepoState {
|
||||
switch (state) {
|
||||
case "opened":
|
||||
return GitRepoState.OPEN;
|
||||
case "closed":
|
||||
return GitRepoState.CLOSED;
|
||||
case "merged":
|
||||
return GitRepoState.MERGED;
|
||||
default:
|
||||
return GitRepoState.LOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
async mapPullRequest(mr: MergeRequestSchema): Promise<GitPullRequest> {
|
||||
// throw new Error("Method not implemented.");
|
||||
return {
|
||||
number: mr.iid,
|
||||
author: mr.author.username,
|
||||
url: mr.web_url,
|
||||
htmlUrl: mr.web_url,
|
||||
title: mr.title,
|
||||
body: mr.description,
|
||||
state: this.mapGitState(mr.state),
|
||||
merged: this.isMerged(mr),
|
||||
mergedBy: mr.merged_by?.username,
|
||||
reviewers: mr.reviewers?.map((r => r.username)) ?? [],
|
||||
assignees: mr.assignees?.map((r => r.username)) ?? [],
|
||||
sourceRepo: await this.mapSourceRepo(mr),
|
||||
targetRepo: await this.mapTargetRepo(mr),
|
||||
nCommits: 1, // info not present on mr
|
||||
// if mr is merged, use merge_commit_sha otherwise use sha
|
||||
// what is the difference between sha and diff_refs.head_sha?
|
||||
commits: this.isMerged(mr) ? [mr.squash_commit_sha ? mr.squash_commit_sha : mr.merge_commit_sha as string] : [mr.sha]
|
||||
};
|
||||
}
|
||||
|
||||
async mapSourceRepo(mr: MergeRequestSchema): Promise<GitRepository> {
|
||||
const project: ProjectSchema = await this.getProject(mr.source_project_id);
|
||||
|
||||
return {
|
||||
owner: project.namespace.full_path, // or just proj.path?
|
||||
project: project.path,
|
||||
cloneUrl: project.http_url_to_repo,
|
||||
};
|
||||
}
|
||||
|
||||
async mapTargetRepo(mr: MergeRequestSchema): Promise<GitRepository> {
|
||||
const project: ProjectSchema = await this.getProject(mr.target_project_id);
|
||||
|
||||
return {
|
||||
owner: project.namespace.full_path, // or just proj.path?
|
||||
project: project.path,
|
||||
cloneUrl: project.http_url_to_repo,
|
||||
};
|
||||
}
|
||||
|
||||
private isMerged(mr: MergeRequestSchema) {
|
||||
return this.mapGitState(mr.state) === GitRepoState.MERGED;
|
||||
}
|
||||
|
||||
private async getProject(projectId: number): Promise<ProjectSchema> {
|
||||
const { data } = await this.client.get(`/projects/${projectId}`);
|
||||
|
||||
if (!data) {
|
||||
throw new Error(`Project ${projectId} not found`);
|
||||
}
|
||||
|
||||
return data as ProjectSchema;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue