copykitku/src/vendor/github/repomgr.ts

321 lines
9.1 KiB
TypeScript
Raw Normal View History

2020-08-21 11:53:55 +02:00
/*
2021-02-14 22:24:01 +01:00
* Copykitku. Copyright (C) 2020 selfisekai <laura@selfisekai.rocks> and other contributors.
2020-08-21 11:53:55 +02:00
*
* 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>.
*/
2020-08-20 17:05:20 +02:00
import {
RepoManager,
Issue,
2021-02-02 16:20:59 +01:00
Commit,
2020-08-20 17:05:20 +02:00
Repo,
ACTOR_TYPE,
ISSUE_STATE,
MergeRequest,
MERGE_REQUEST_STATE,
MERGE_REQUEST_MERGABILITY,
2021-02-01 12:04:19 +01:00
ENTITY_TYPE,
2020-08-20 17:05:20 +02:00
} from '../../types';
2020-08-05 23:33:36 +02:00
import GitHubVendorManager from './vendormgr';
import assert from 'assert';
2021-02-21 21:29:21 +01:00
import {
2021-02-26 00:58:36 +01:00
GHCommit,
GHGitObject,
2021-02-21 21:29:21 +01:00
GHMutation,
GHMutationCreateIssueArgs,
GHMutationCreatePullRequestArgs,
GHPullRequest,
GHPullRequestCommit,
} from './api-types';
2020-08-05 23:33:36 +02:00
export default class GitHubRepoManager implements RepoManager {
vendorMgr: GitHubVendorManager;
repo: Repo;
2020-08-26 21:34:43 +02:00
repoId: string;
2020-08-05 23:33:36 +02:00
constructor(vendorMgr: GitHubVendorManager, repoPath: string) {
this.vendorMgr = vendorMgr;
const mobj = /^([^/\s]+)\/([^/\s]+)$/.exec(repoPath);
assert(mobj, 'invalid repo path');
const [, owner, repoName] = mobj;
this.repo = {
vendor: this.vendorMgr.vendor,
owner: {
type: ACTOR_TYPE.UNKNOWN,
username: owner,
vendor: this.vendorMgr.vendor,
},
name: repoName,
2021-02-01 13:59:57 +01:00
url: `https://${this.vendorMgr.vendor.domain}/${repoPath}`,
2020-08-05 23:33:36 +02:00
};
2020-08-26 21:34:43 +02:00
this.repoId = '';
2020-08-05 23:33:36 +02:00
}
public async initialize() {
const meta = await this.vendorMgr._doRequest(
`
query Query($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
2020-08-26 21:34:43 +02:00
id
2020-08-05 23:33:36 +02:00
name
owner {
login
__typename
}
}
}
`,
{
owner: this.repo.owner.username,
name: this.repo.name,
},
);
assert(meta.repository);
assert(meta.repository.owner);
2020-08-26 21:34:43 +02:00
this.repoId = meta.repository.id;
2020-08-05 23:33:36 +02:00
this.repo.name = meta.repository.name;
this.repo.owner.username = meta.repository.owner.login;
// @ts-ignore graphql-codegen ignores built-in graphql values
switch (meta.repository.owner.__typename) {
case 'Organization':
this.repo.owner.type = ACTOR_TYPE.ORG;
break;
case 'User':
this.repo.owner.type = ACTOR_TYPE.USER;
break;
}
return this;
}
public async getIssue(number: string): Promise<Issue> {
const resp = await this.vendorMgr._doRequest(
`
query Query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
issue(number: $number) {
2021-02-01 13:59:57 +01:00
number
2020-08-05 23:33:36 +02:00
title
body
closed
}
}
}
`,
{
owner: this.repo.owner.username,
name: this.repo.name,
number: parseInt(number, 10),
},
);
assert(resp.repository, 'no repository');
assert(resp.repository.issue, 'no issue');
const { issue } = resp.repository;
return {
2021-02-01 12:04:19 +01:00
type: ENTITY_TYPE.ISSUE,
2020-08-20 17:05:20 +02:00
id: number,
2020-08-05 23:33:36 +02:00
content: issue.body,
title: issue.title,
repo: this.repo,
2020-08-20 17:05:20 +02:00
state: issue.closed ? ISSUE_STATE.CLOSED : ISSUE_STATE.OPEN,
2021-02-01 13:59:57 +01:00
url: `${this.repo.url}/issues/${issue.number}`,
2020-08-20 17:05:20 +02:00
};
}
2020-08-26 21:34:43 +02:00
public async replicateIssue(issue: Issue) {
const resp = await this.vendorMgr._doRequest<GHMutation, GHMutationCreateIssueArgs>(
`
mutation ($input: CreateIssueInput!) {
createIssue(input: $input) {
issue {
number
title
body
closed
}
}
}
`,
{
input: {
repositoryId: this.repoId,
title: issue.title,
body: issue.content,
},
},
);
assert(resp.createIssue, 'creating issue failed');
assert(resp.createIssue.issue, 'creating issue failed');
const replicated = resp.createIssue.issue;
return {
2021-02-01 12:15:53 +01:00
type: ENTITY_TYPE.ISSUE as const,
2020-08-26 21:34:43 +02:00
id: replicated.number.toString(),
content: replicated.body,
title: replicated.title,
repo: this.repo,
state: replicated.closed ? ISSUE_STATE.CLOSED : ISSUE_STATE.OPEN,
2021-02-01 13:59:57 +01:00
url: `${this.repo.url}/issues/${replicated.number}`,
2020-08-26 21:34:43 +02:00
};
}
2021-02-21 21:29:21 +01:00
protected async _parsePR(pullRequest: GHPullRequest): Promise<MergeRequest> {
2021-02-02 16:20:59 +01:00
assert(pullRequest.commits.nodes, 'no commits in pull request (?)');
assert(
pullRequest.commits.nodes.length === pullRequest.commits.totalCount,
"no replicating today, github is fucked up and the commit pagination doesn't fucking work",
);
2020-08-20 17:05:20 +02:00
return {
2021-02-01 12:04:19 +01:00
type: ENTITY_TYPE.MERGE_REQUEST,
2021-02-21 21:29:21 +01:00
id: pullRequest.number.toString(),
2020-08-20 17:05:20 +02:00
content: pullRequest.body,
title: pullRequest.title,
repo: this.repo,
state: {
OPEN: MERGE_REQUEST_STATE.OPEN,
CLOSED: MERGE_REQUEST_STATE.CLOSED,
MERGED: MERGE_REQUEST_STATE.MERGED,
}[pullRequest.state],
2020-08-20 17:05:20 +02:00
mergability: {
MERGEABLE: MERGE_REQUEST_MERGABILITY.MERGEABLE,
CONFLICTING: MERGE_REQUEST_MERGABILITY.CONFLICTING,
UNKNOWN: null,
}[pullRequest.mergeable],
2021-02-02 16:20:59 +01:00
// for some fucking reason GitHub declared that the array of commits could contain null values
commits: (pullRequest.commits.nodes.filter((n) => !!n) as GHPullRequestCommit[])
.map((n) => n.commit)
2021-02-26 00:58:36 +01:00
.map(this._parseCommit),
2020-08-20 17:05:20 +02:00
isDraft: pullRequest.isDraft,
2021-02-01 13:59:57 +01:00
url: `${this.repo.url}/pull/${pullRequest.number}`,
2020-08-05 23:33:36 +02:00
};
}
2021-02-21 21:29:21 +01:00
public async getMergeRequest(number: string): Promise<MergeRequest> {
const resp = await this.vendorMgr._doRequest(
`
query Query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
number
title
body
isDraft
mergeable
state
commits(first: 250) {
nodes {
commit {
messageHeadline
messageBody
oid
}
}
totalCount
}
}
}
}
`,
{
owner: this.repo.owner.username,
name: this.repo.name,
number: parseInt(number, 10),
},
);
assert(resp.repository, 'no repository');
assert(resp.repository.pullRequest, 'no pull request');
return this._parsePR(resp.repository.pullRequest);
}
public async replicateMergeRequest(
mergeRequest: MergeRequest,
destBranch: string,
targetBranch: string,
) {
const resp = await this.vendorMgr._doRequest<GHMutation, GHMutationCreatePullRequestArgs>(
`
mutation ($input: CreatePullRequestInput!) {
createPullRequest(input: $input) {
pullRequest {
number
title
body
isDraft
mergeable
state
commits(first: 250) {
nodes {
commit {
messageHeadline
messageBody
oid
}
}
totalCount
}
}
}
}
`,
{
input: {
repositoryId: this.repoId,
title: mergeRequest.title,
body: mergeRequest.content,
baseRefName: targetBranch,
headRefName: destBranch,
},
},
);
assert(resp.createPullRequest, 'no pull request creation object');
assert(resp.createPullRequest.pullRequest, 'no pull request');
return this._parsePR(resp.createPullRequest.pullRequest);
}
2021-02-26 00:58:36 +01:00
protected _parseCommit(commit: GHCommit): Commit {
return {
type: ENTITY_TYPE.COMMIT,
id: commit.oid,
title: commit.messageHeadline,
content: commit.messageBody,
repo: this.repo,
url: `${this.repo.url}/commit/${commit.oid}`,
patchURL: `${this.repo.url}/commit/${commit.oid}.patch`,
patchContent: () => this.vendorMgr._http_get(`${this.repo.url}/commit/${commit.oid}.patch`),
diffURL: `${this.repo.url}/commit/${commit.oid}.diff`,
diffContent: () => this.vendorMgr._http_get(`${this.repo.url}/commit/${commit.oid}.diff`),
};
}
public async getCommit(oid: string) {
const resp = await this.vendorMgr._doRequest(
`
query Query($owner: String!, $name: String!, $oid: GitObjectID!) {
repository(owner: $owner, name: $name) {
object(oid: $oid) {
__typename
... on Commit {
messageHeadline
messageBody
oid
}
}
}
}
`,
{
owner: this.repo.owner.username,
name: this.repo.name,
oid: oid,
},
);
assert(resp.repository, 'no repository');
assert(resp.repository.object, 'no repository object');
const object = resp.repository.object as GHGitObject | GHCommit;
assert('__typename' in object && object.__typename === 'Commit', 'object is not a commit (?)');
return this._parseCommit(object);
}
2020-08-05 23:33:36 +02:00
}