full MR replication
This commit is contained in:
parent
20cfde5ea3
commit
859f3caf39
|
@ -20,6 +20,14 @@ export default class Replicate extends Command {
|
|||
description: 'do not commit the applied changes (for MRs)',
|
||||
allowNo: false,
|
||||
}),
|
||||
remote: flags.string({
|
||||
description: 'git remote where the commits are pushed (for MRs)',
|
||||
default: 'origin',
|
||||
}),
|
||||
targetBranch: flags.string({
|
||||
description: 'branch to which the MR should target',
|
||||
default: 'master', // TODO: check in the repository instead of taking a wild guess
|
||||
}),
|
||||
};
|
||||
|
||||
static args = [];
|
||||
|
@ -27,7 +35,7 @@ export default class Replicate extends Command {
|
|||
async run() {
|
||||
const { flags } = this.parse(Replicate);
|
||||
|
||||
const { destBranch, doNotCommit } = flags;
|
||||
const { destBranch, doNotCommit, remote, targetBranch } = flags;
|
||||
|
||||
const sourcePath = parsePath(flags.source);
|
||||
const destPath = parsePath(flags.dest);
|
||||
|
@ -54,10 +62,16 @@ export default class Replicate extends Command {
|
|||
const repl = await kitku.replicateMergeRequest(sourceMR, destRepo, {
|
||||
destBranch,
|
||||
doNotCommit,
|
||||
remote,
|
||||
targetBranch,
|
||||
});
|
||||
// Copykitku.replicateMergeRequest() does not replicate MRs fully yet, it just applies the commits.
|
||||
// uncomment the line below once the replication is done and the method returns a MergeRequest object
|
||||
// console.log(`Replicated successfully: ${repl.url}`);
|
||||
if (repl === true) {
|
||||
// patches got applied to the branch, without pushing and creating a MR (due to --doNotCommit)
|
||||
console.log('Replicated commits successfully');
|
||||
} else {
|
||||
// commits got pushed and a MR was created
|
||||
console.log(`Replicated successfully: ${repl.url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ENTITY_TYPE.COMMIT: {
|
||||
|
|
|
@ -19,9 +19,7 @@ import {
|
|||
RepoManager,
|
||||
VENDOR_TYPE,
|
||||
MergeRequest,
|
||||
RepoElement,
|
||||
Commit,
|
||||
ENTITY_TYPE,
|
||||
} from './types';
|
||||
import { getConfig, DEFAULT_CONFIG } from './utils';
|
||||
|
||||
|
@ -60,15 +58,34 @@ export default class Copykitku {
|
|||
public async replicateMergeRequest(
|
||||
sourceMR: MergeRequest,
|
||||
destination: RepoManager,
|
||||
opts: { destBranch?: string; doNotCommit?: boolean | null } = {},
|
||||
opts: {
|
||||
destBranch?: string;
|
||||
doNotCommit?: boolean | null;
|
||||
doNotPush?: boolean | null;
|
||||
remote: string;
|
||||
targetBranch: string;
|
||||
},
|
||||
) {
|
||||
const destBranch =
|
||||
opts.destBranch || `${sourceMR.repo.owner.username}/${sourceMR.repo.name}/mr-${sourceMR.id}`;
|
||||
const { doNotCommit } = opts;
|
||||
const { doNotCommit, doNotPush, remote, targetBranch } = opts;
|
||||
|
||||
await this.replicateCommits(sourceMR.commits, destination, { destBranch, doNotCommit });
|
||||
|
||||
// TODO: push commits to the repository and create the MR (with option to disable this behaviour)
|
||||
if (doNotCommit !== true && doNotPush !== true) {
|
||||
const git = simpleGit({ baseDir: process.cwd() });
|
||||
git.push(remote, destBranch);
|
||||
|
||||
// if markdown support on destination
|
||||
if ([VENDOR_TYPE.GITHUB, VENDOR_TYPE.GITLAB].includes(destination.repo.vendor.type)) {
|
||||
sourceMR.content += `\n\nReplicated from ${sourceMR.url} with [Copykitku](https://git.sakamoto.pl/laudompat/copykitku)`;
|
||||
} else {
|
||||
sourceMR.content += `\n\nReplicated from ${sourceMR.url} with https://git.sakamoto.pl/laudompat/copykitku`;
|
||||
}
|
||||
|
||||
return destination.replicateMergeRequest(sourceMR, destBranch, targetBranch);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async replicateCommits(
|
||||
|
|
|
@ -71,6 +71,14 @@ export interface RepoManager {
|
|||
getIssue: (id: string) => Promise<Issue>;
|
||||
replicateIssue: (issue: Issue) => Promise<Issue>;
|
||||
getMergeRequest: (id: string) => Promise<MergeRequest>;
|
||||
/** this method is to be invoked after the commits are pushed to the branch */
|
||||
replicateMergeRequest: (
|
||||
mergeRequest: MergeRequest,
|
||||
/** where the commits have been pushed */
|
||||
destBranch: string,
|
||||
/** where the newly-created MR should target */
|
||||
targetBranch: string,
|
||||
) => Promise<MergeRequest>;
|
||||
}
|
||||
|
||||
export enum ENTITY_TYPE {
|
||||
|
|
134
src/vendor/github/repomgr.ts
vendored
134
src/vendor/github/repomgr.ts
vendored
|
@ -20,7 +20,13 @@ import {
|
|||
} from '../../types';
|
||||
import GitHubVendorManager from './vendormgr';
|
||||
import assert from 'assert';
|
||||
import { GHMutation, GHMutationCreateIssueArgs, GHPullRequestCommit } from './api-types';
|
||||
import {
|
||||
GHMutation,
|
||||
GHMutationCreateIssueArgs,
|
||||
GHMutationCreatePullRequestArgs,
|
||||
GHPullRequest,
|
||||
GHPullRequestCommit,
|
||||
} from './api-types';
|
||||
|
||||
export default class GitHubRepoManager implements RepoManager {
|
||||
vendorMgr: GitHubVendorManager;
|
||||
|
@ -153,6 +159,48 @@ export default class GitHubRepoManager implements RepoManager {
|
|||
};
|
||||
}
|
||||
|
||||
protected async _parsePR(pullRequest: GHPullRequest): Promise<MergeRequest> {
|
||||
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",
|
||||
);
|
||||
return {
|
||||
type: ENTITY_TYPE.MERGE_REQUEST,
|
||||
id: pullRequest.number.toString(),
|
||||
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],
|
||||
mergability: {
|
||||
MERGEABLE: MERGE_REQUEST_MERGABILITY.MERGEABLE,
|
||||
CONFLICTING: MERGE_REQUEST_MERGABILITY.CONFLICTING,
|
||||
UNKNOWN: null,
|
||||
}[pullRequest.mergeable],
|
||||
// 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)
|
||||
.map<Commit>((c) => ({
|
||||
type: ENTITY_TYPE.COMMIT,
|
||||
id: c.oid,
|
||||
title: c.messageHeadline,
|
||||
content: c.messageBody,
|
||||
repo: this.repo,
|
||||
url: `${this.repo.url}/commit/${c.oid}`,
|
||||
patchURL: `${this.repo.url}/commit/${c.oid}.patch`,
|
||||
patchContent: () => this.vendorMgr._http_get(`${this.repo.url}/commit/${c.oid}.patch`),
|
||||
diffURL: `${this.repo.url}/commit/${c.oid}.diff`,
|
||||
diffContent: () => this.vendorMgr._http_get(`${this.repo.url}/commit/${c.oid}.diff`),
|
||||
})),
|
||||
isDraft: pullRequest.isDraft,
|
||||
url: `${this.repo.url}/pull/${pullRequest.number}`,
|
||||
};
|
||||
}
|
||||
|
||||
public async getMergeRequest(number: string): Promise<MergeRequest> {
|
||||
const resp = await this.vendorMgr._doRequest(
|
||||
`
|
||||
|
@ -187,45 +235,51 @@ export default class GitHubRepoManager implements RepoManager {
|
|||
);
|
||||
assert(resp.repository, 'no repository');
|
||||
assert(resp.repository.pullRequest, 'no pull request');
|
||||
const { pullRequest } = resp.repository;
|
||||
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",
|
||||
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,
|
||||
},
|
||||
},
|
||||
);
|
||||
return {
|
||||
type: ENTITY_TYPE.MERGE_REQUEST,
|
||||
id: number,
|
||||
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],
|
||||
mergability: {
|
||||
MERGEABLE: MERGE_REQUEST_MERGABILITY.MERGEABLE,
|
||||
CONFLICTING: MERGE_REQUEST_MERGABILITY.CONFLICTING,
|
||||
UNKNOWN: null,
|
||||
}[pullRequest.mergeable],
|
||||
// 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)
|
||||
.map<Commit>((c) => ({
|
||||
type: ENTITY_TYPE.COMMIT,
|
||||
id: c.oid,
|
||||
title: c.messageHeadline,
|
||||
content: c.messageBody,
|
||||
repo: this.repo,
|
||||
url: `${this.repo.url}/commit/${c.oid}`,
|
||||
patchURL: `${this.repo.url}/commit/${c.oid}.patch`,
|
||||
patchContent: () => this.vendorMgr._http_get(`${this.repo.url}/commit/${c.oid}.patch`),
|
||||
diffURL: `${this.repo.url}/commit/${c.oid}.diff`,
|
||||
diffContent: () => this.vendorMgr._http_get(`${this.repo.url}/commit/${c.oid}.diff`),
|
||||
})),
|
||||
isDraft: pullRequest.isDraft,
|
||||
url: `${this.repo.url}/pull/${pullRequest.number}`,
|
||||
};
|
||||
assert(resp.createPullRequest, 'no pull request creation object');
|
||||
assert(resp.createPullRequest.pullRequest, 'no pull request');
|
||||
return this._parsePR(resp.createPullRequest.pullRequest);
|
||||
}
|
||||
}
|
||||
|
|
106
src/vendor/gitlab/repomgr.ts
vendored
106
src/vendor/gitlab/repomgr.ts
vendored
|
@ -18,7 +18,13 @@ import {
|
|||
MERGE_REQUEST_MERGABILITY,
|
||||
ENTITY_TYPE,
|
||||
} from '../../types';
|
||||
import { GLIssue, GLMutation, GLMutationCreateIssueArgs } from './api-types';
|
||||
import {
|
||||
GLIssue,
|
||||
GLMergeRequest,
|
||||
GLMutation,
|
||||
GLMutationCreateIssueArgs,
|
||||
GLMutationMergeRequestCreateArgs,
|
||||
} from './api-types';
|
||||
import { GL4MergeRequestCommit } from './rest-api-types';
|
||||
import GitLabVendorManager from './vendormgr';
|
||||
import assert from 'assert';
|
||||
|
@ -117,7 +123,7 @@ export default class GitHubRepoManager implements RepoManager {
|
|||
return this._parseIssue(issue);
|
||||
}
|
||||
|
||||
public _parseIssue(issue: GLIssue): Issue {
|
||||
protected _parseIssue(issue: GLIssue): Issue {
|
||||
return {
|
||||
type: ENTITY_TYPE.ISSUE,
|
||||
id: issue.iid,
|
||||
|
@ -165,39 +171,16 @@ export default class GitHubRepoManager implements RepoManager {
|
|||
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;
|
||||
protected async _parseMR(mergeRequest: GLMergeRequest): Promise<MergeRequest> {
|
||||
// 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`,
|
||||
`projects/${encodeURIComponent(this.repoId)}/merge_requests/${mergeRequest.iid}/commits`,
|
||||
);
|
||||
return {
|
||||
type: ENTITY_TYPE.MERGE_REQUEST,
|
||||
id: number,
|
||||
id: mergeRequest.iid,
|
||||
content: mergeRequest.description || '',
|
||||
title: mergeRequest.title,
|
||||
repo: this.repo,
|
||||
|
@ -231,4 +214,71 @@ export default class GitHubRepoManager implements RepoManager {
|
|||
url: `${this.repo.url}/-/merge_requests/${mergeRequest.iid}`,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
return this._parseMR(mergeRequest);
|
||||
}
|
||||
|
||||
public async replicateMergeRequest(
|
||||
mergeRequest: MergeRequest,
|
||||
destBranch: string,
|
||||
targetBranch: string,
|
||||
) {
|
||||
const resp = await this.vendorMgr._doRequest_gql<GLMutation, GLMutationMergeRequestCreateArgs>(
|
||||
`
|
||||
mutation ($input: MergeRequestCreateInput!) {
|
||||
mergeRequestCreate(input: $input) {
|
||||
mergeRequest {
|
||||
iid
|
||||
title
|
||||
description
|
||||
state
|
||||
mergeStatus
|
||||
workInProgress
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
input: {
|
||||
projectPath: this.repoPath,
|
||||
sourceBranch: destBranch,
|
||||
targetBranch: targetBranch,
|
||||
title: mergeRequest.title,
|
||||
description: mergeRequest.content,
|
||||
},
|
||||
},
|
||||
);
|
||||
assert(resp.mergeRequestCreate, 'creating MR failed for unknown reason');
|
||||
assert(
|
||||
!Array.isArray(resp.mergeRequestCreate.errors) || resp.mergeRequestCreate.errors.length === 0,
|
||||
`GitLab said: ${(resp.mergeRequestCreate.errors || []).map((e) => `"${e}"`).join(', ')}`,
|
||||
);
|
||||
assert(resp.mergeRequestCreate.mergeRequest, 'creating MR failed for unknown reason (2)');
|
||||
return this._parseMR(resp.mergeRequestCreate.mergeRequest);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue