235 lines
7.1 KiB
TypeScript
235 lines
7.1 KiB
TypeScript
/*
|
|
* Copycat. Copyright (C) 2020 selfisekai <laura@selfisekai.rocks> and other contributors.
|
|
*
|
|
* This is free software, and you are welcome to redistribute it
|
|
* under the GNU General Public License 3.0 or later; see the LICENSE file for details,
|
|
* or, if the file is unavailable, visit <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
|
|
*/
|
|
|
|
import {
|
|
RepoManager,
|
|
Issue,
|
|
Repo,
|
|
Commit,
|
|
ACTOR_TYPE,
|
|
ISSUE_STATE,
|
|
MergeRequest,
|
|
MERGE_REQUEST_STATE,
|
|
MERGE_REQUEST_MERGABILITY,
|
|
ENTITY_TYPE,
|
|
} from '../../types';
|
|
import { GLIssue, GLMutation, GLMutationCreateIssueArgs } from './api-types';
|
|
import { GL4MergeRequestCommit } from './rest-api-types';
|
|
import GitLabVendorManager from './vendormgr';
|
|
import assert from 'assert';
|
|
|
|
export default class GitHubRepoManager implements RepoManager {
|
|
vendorMgr: GitLabVendorManager;
|
|
repo: Repo;
|
|
repoPath: string;
|
|
repoId: string;
|
|
|
|
constructor(vendorMgr: GitLabVendorManager, repoPath: string) {
|
|
this.vendorMgr = vendorMgr;
|
|
|
|
const mobj = /^([^/\s]+)\/([^\s]+)$/.exec(repoPath);
|
|
assert(mobj, 'invalid repo path');
|
|
// "small path" may be just repository name
|
|
// or the whole path except the top-level owner
|
|
const [, owner, repoSmallPath] = mobj;
|
|
|
|
this.repo = {
|
|
vendor: this.vendorMgr.vendor,
|
|
owner: {
|
|
type: ACTOR_TYPE.UNKNOWN,
|
|
username: owner,
|
|
vendor: this.vendorMgr.vendor,
|
|
},
|
|
name: repoSmallPath,
|
|
url: `https://${this.vendorMgr.vendor.domain}/${repoPath}`,
|
|
};
|
|
this.repoPath = repoPath;
|
|
this.repoId = ''; // for strict null check, is replaced in .initialize
|
|
}
|
|
|
|
public async initialize() {
|
|
const meta = await this.vendorMgr._doRequest_gql(
|
|
`
|
|
query ($path: ID!, $ownerStr: String!, $ownerID: ID!) {
|
|
project(fullPath: $path) {
|
|
id
|
|
path
|
|
}
|
|
group(fullPath: $ownerID) {
|
|
id
|
|
}
|
|
user(username: $ownerStr) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
{
|
|
path: this.repoPath,
|
|
// there's no way to check if the namespace is a user or group,
|
|
// so we check if there's anyone using that username
|
|
// but one is a String, and one is an ID
|
|
// and these types just don't overlap 🤷♀️
|
|
ownerStr: this.repo.owner.username,
|
|
ownerID: this.repo.owner.username,
|
|
},
|
|
);
|
|
assert(meta.project, 'no project');
|
|
assert(meta.group || meta.user, 'no top-level project owner');
|
|
const [, repoId] = /\/(\d+)$/.exec(meta.project.id) || [,];
|
|
assert(repoId, 'broken project id');
|
|
this.repoId = repoId;
|
|
this.repo.owner.username = meta.project.path;
|
|
if (meta.group) {
|
|
this.repo.owner.type = ACTOR_TYPE.ORG;
|
|
} else if (meta.user) {
|
|
this.repo.owner.type = ACTOR_TYPE.USER;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public async getIssue(number: string): Promise<Issue> {
|
|
const resp = await this.vendorMgr._doRequest_gql(
|
|
`
|
|
query ($path: ID!, $id: String!) {
|
|
project(fullPath: $path) {
|
|
issue(iid: $id) {
|
|
iid
|
|
title
|
|
description
|
|
state
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
{
|
|
path: this.repoPath,
|
|
id: number,
|
|
},
|
|
);
|
|
assert(resp.project, 'no project');
|
|
assert(resp.project.issue, 'no issue');
|
|
const { issue } = resp.project;
|
|
return this._parseIssue(issue);
|
|
}
|
|
|
|
public _parseIssue(issue: GLIssue): Issue {
|
|
return {
|
|
type: ENTITY_TYPE.ISSUE,
|
|
id: issue.iid,
|
|
content: issue.description || '',
|
|
title: issue.title,
|
|
repo: this.repo,
|
|
state: {
|
|
opened: ISSUE_STATE.OPEN,
|
|
closed: ISSUE_STATE.CLOSED,
|
|
locked: ISSUE_STATE.CLOSED,
|
|
all: ISSUE_STATE.CLOSED, // today's fact: gitlab api is fucked up
|
|
}[issue.state],
|
|
url: `${this.repo.url}/-/issues/${issue.iid}`,
|
|
};
|
|
}
|
|
|
|
public async replicateIssue(issue: Issue) {
|
|
const resp = await this.vendorMgr._doRequest_gql<GLMutation, GLMutationCreateIssueArgs>(
|
|
`
|
|
mutation ($input: CreateIssueInput!) {
|
|
createIssue(input: $input) {
|
|
issue {
|
|
iid
|
|
title
|
|
description
|
|
state
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
{
|
|
input: {
|
|
projectPath: this.repoPath,
|
|
title: issue.title,
|
|
description: issue.content,
|
|
},
|
|
},
|
|
);
|
|
assert(resp.createIssue, 'creating issue failed for unknown reason');
|
|
assert(
|
|
!Array.isArray(resp.createIssue.errors) || resp.createIssue.errors.length === 0,
|
|
`GitLab said: ${(resp.createIssue.errors || []).map((e) => `"${e}"`).join(', ')}`,
|
|
);
|
|
assert(resp.createIssue.issue, 'creating issue failed for unknown reason (2)');
|
|
return this._parseIssue(resp.createIssue.issue);
|
|
}
|
|
|
|
public async getMergeRequest(number: string): Promise<MergeRequest> {
|
|
const resp = await this.vendorMgr._doRequest_gql(
|
|
`
|
|
query ($path: ID!, $id: String!) {
|
|
project(fullPath: $path) {
|
|
mergeRequest(iid: $id) {
|
|
iid
|
|
title
|
|
description
|
|
state
|
|
mergeStatus
|
|
workInProgress
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
{
|
|
path: this.repoPath,
|
|
id: number,
|
|
},
|
|
);
|
|
assert(resp.project, 'no project');
|
|
assert(resp.project.mergeRequest, 'no merge request');
|
|
const { mergeRequest } = resp.project;
|
|
// for some fucking reason this is not available under the GQL API
|
|
// https://gitlab.com/gitlab-org/gitlab/-/issues/300780
|
|
const commits = await this.vendorMgr._doRequest_v4<GL4MergeRequestCommit[]>(
|
|
'GET',
|
|
`projects/${encodeURIComponent(this.repoId)}/merge_requests/${number}/commits`,
|
|
);
|
|
return {
|
|
type: ENTITY_TYPE.MERGE_REQUEST,
|
|
id: number,
|
|
content: mergeRequest.description || '',
|
|
title: mergeRequest.title,
|
|
repo: this.repo,
|
|
state: {
|
|
opened: MERGE_REQUEST_STATE.OPEN,
|
|
closed: MERGE_REQUEST_STATE.CLOSED,
|
|
merged: MERGE_REQUEST_STATE.MERGED,
|
|
locked: MERGE_REQUEST_STATE.CLOSED,
|
|
all: MERGE_REQUEST_STATE.CLOSED, // today's fact: gitlab api is fucked up
|
|
}[mergeRequest.state],
|
|
mergability:
|
|
({
|
|
can_be_merged: MERGE_REQUEST_MERGABILITY.MERGEABLE,
|
|
cannot_be_merged: MERGE_REQUEST_MERGABILITY.CONFLICTING,
|
|
} as { [key: string]: MERGE_REQUEST_MERGABILITY | undefined })[
|
|
mergeRequest.mergeStatus || ''
|
|
] || null,
|
|
isDraft: mergeRequest.workInProgress,
|
|
commits: commits.map<Commit>((c) => ({
|
|
type: ENTITY_TYPE.COMMIT,
|
|
id: c.id,
|
|
title: c.title,
|
|
content: c.message,
|
|
repo: this.repo,
|
|
url: `${this.repo.url}/-/commit/${c.id}`,
|
|
diffURL: `${this.repo.url}/-/commit/${c.id}.diff`,
|
|
diffContent: () => this.vendorMgr._http_get(`${this.repo.url}/-/commit/${c.id}.diff`),
|
|
patchURL: `${this.repo.url}/-/commit/${c.id}.patch`,
|
|
patchContent: () => this.vendorMgr._http_get(`${this.repo.url}/-/commit/${c.id}.patch`),
|
|
})),
|
|
url: `${this.repo.url}/-/merge_requests/${mergeRequest.iid}`,
|
|
};
|
|
}
|
|
}
|