base MR commit replication
This commit is contained in:
parent
abcffc8b7a
commit
13de9bd4aa
|
@ -48,6 +48,7 @@
|
|||
"@oclif/config": "^1.17.0",
|
||||
"@oclif/plugin-help": "^3.2.1",
|
||||
"appdata-path": "^1.0.0",
|
||||
"email-addresses": "^4.0.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"got": "^11.8.1",
|
||||
"graphql": "^15.5.0",
|
||||
|
@ -59,6 +60,7 @@
|
|||
"@graphql-codegen/introspection": "^1.18.1",
|
||||
"@graphql-codegen/typescript": "^1.20.2",
|
||||
"@oclif/dev-cli": "^1.26.0",
|
||||
"@types/email-addresses": "^3.0.0",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/iarna__toml": "^2.0.1",
|
||||
"@types/inquirer": "^7.3.1",
|
||||
|
|
|
@ -12,6 +12,14 @@ export default class Replicate extends Command {
|
|||
|
||||
source: flags.string({ char: 's', required: true }),
|
||||
dest: flags.string({ char: 'd', required: true }),
|
||||
|
||||
destBranch: flags.string({
|
||||
description: 'name of the new branch (optional for MRs, required for commits)',
|
||||
}),
|
||||
doNotCommit: flags.boolean({
|
||||
description: 'do not commit the applied changes (for MRs)',
|
||||
allowNo: false,
|
||||
}),
|
||||
};
|
||||
|
||||
static args = [];
|
||||
|
@ -19,22 +27,54 @@ export default class Replicate extends Command {
|
|||
async run() {
|
||||
const { flags } = this.parse(Replicate);
|
||||
|
||||
const { destBranch, doNotCommit } = flags;
|
||||
|
||||
const sourcePath = parsePath(flags.source);
|
||||
const destPath = parsePath(flags.dest);
|
||||
|
||||
assert(sourcePath.entity === ENTITY_TYPE.ISSUE, 'Only issues are supported now');
|
||||
assert(sourcePath.entityID, 'Source must be a repo element, not a repo itself');
|
||||
|
||||
const cc = new Copykitku();
|
||||
await cc.initialize();
|
||||
const sourceVendor = cc.vendorManagers.find((v) => v.vendor.domain === sourcePath.domain);
|
||||
const kitku = new Copykitku();
|
||||
await kitku.initialize();
|
||||
const sourceVendor = kitku.vendorManagers.find((v) => v.vendor.domain === sourcePath.domain);
|
||||
assert(sourceVendor, 'Source vendor not found in config');
|
||||
const destVendor = cc.vendorManagers.find((v) => v.vendor.domain === destPath.domain);
|
||||
const destVendor = kitku.vendorManagers.find((v) => v.vendor.domain === destPath.domain);
|
||||
assert(destVendor, 'Destination vendor not found in config');
|
||||
const sourceRepo = await sourceVendor.getRepo(sourcePath.path);
|
||||
const destRepo = await destVendor.getRepo(destPath.path);
|
||||
const sourceEntity = await sourceRepo.getIssue(sourcePath.entityID);
|
||||
sourceEntity.content += `\n\nReplicated from ${sourceEntity.url} with [Copykitku](https://git.sakamoto.pl/laudompat/copykitku)`;
|
||||
const replicatedEntity = await destRepo.replicateIssue(sourceEntity);
|
||||
console.log(`Replicated successfully: ${replicatedEntity.url}`);
|
||||
switch (sourcePath.entity) {
|
||||
case ENTITY_TYPE.ISSUE: {
|
||||
const sourceIssue = await sourceRepo.getIssue(sourcePath.entityID);
|
||||
const repl = await kitku.replicateIssue(sourceIssue, destRepo);
|
||||
console.log(`Replicated successfully: ${repl.url}`);
|
||||
break;
|
||||
}
|
||||
case ENTITY_TYPE.MERGE_REQUEST: {
|
||||
const sourceMR = await sourceRepo.getMergeRequest(sourcePath.entityID);
|
||||
const repl = await kitku.replicateMergeRequest(sourceMR, destRepo, {
|
||||
destBranch,
|
||||
doNotCommit,
|
||||
});
|
||||
// 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}`);
|
||||
break;
|
||||
}
|
||||
case ENTITY_TYPE.COMMIT: {
|
||||
// there's no way to get a single commit from repository yet
|
||||
/*
|
||||
const sourceCommit = await sourceRepo.getCommit(sourcePath.entityID);
|
||||
const repl = await kitku.replicateCommits(sourceCommit, destRepo, {
|
||||
destBranch,
|
||||
doNotCommit,
|
||||
});
|
||||
*/
|
||||
console.log('No commit replication yet, sorry');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown entity type');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,22 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { VendorManager, CopykitkuConfig, CopykitkuProfile } from './types';
|
||||
import simpleGit from 'simple-git';
|
||||
import addresses from 'email-addresses';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import {
|
||||
VendorManager,
|
||||
CopykitkuConfig,
|
||||
CopykitkuProfile,
|
||||
Issue,
|
||||
RepoManager,
|
||||
VENDOR_TYPE,
|
||||
MergeRequest,
|
||||
RepoElement,
|
||||
Commit,
|
||||
ENTITY_TYPE,
|
||||
} from './types';
|
||||
import { getConfig, DEFAULT_CONFIG } from './utils';
|
||||
|
||||
export default class Copykitku {
|
||||
|
@ -30,4 +45,82 @@ export default class Copykitku {
|
|||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async replicateIssue(sourceIssue: Issue, destination: RepoManager) {
|
||||
// if markdown support on destination
|
||||
if ([VENDOR_TYPE.GITHUB, VENDOR_TYPE.GITLAB].includes(destination.repo.vendor.type)) {
|
||||
sourceIssue.content += `\n\nReplicated from ${sourceIssue.url} with [Copykitku](https://git.sakamoto.pl/laudompat/copykitku)`;
|
||||
} else {
|
||||
sourceIssue.content += `\n\nReplicated from ${sourceIssue.url} with https://git.sakamoto.pl/laudompat/copykitku`;
|
||||
}
|
||||
|
||||
return destination.replicateIssue(sourceIssue);
|
||||
}
|
||||
|
||||
public async replicateMergeRequest(
|
||||
sourceMR: MergeRequest,
|
||||
destination: RepoManager,
|
||||
opts: { destBranch?: string; doNotCommit?: boolean | null } = {},
|
||||
) {
|
||||
const destBranch =
|
||||
opts.destBranch || `${sourceMR.repo.owner.username}/${sourceMR.repo.name}/mr-${sourceMR.id}`;
|
||||
const { doNotCommit } = 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)
|
||||
}
|
||||
|
||||
public async replicateCommits(
|
||||
sourceCommit: Commit | Commit[],
|
||||
destination: RepoManager,
|
||||
opts: { destBranch: string; doNotCommit?: boolean | null },
|
||||
) {
|
||||
const { destBranch, doNotCommit } = opts;
|
||||
const commits = Array.isArray(sourceCommit) ? sourceCommit : [sourceCommit];
|
||||
|
||||
// saving patch files to /tmp or equivalent
|
||||
const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), 'copykitku-'));
|
||||
const patchFiles = await Promise.all(
|
||||
commits.map(async (c, index) => {
|
||||
const commit = c as Commit & {
|
||||
patchFile: string;
|
||||
from: string;
|
||||
};
|
||||
const filename = path.join(
|
||||
tmpPath,
|
||||
`${index.toString().padStart(4, '0')}-${commit.id}.patch`,
|
||||
);
|
||||
const content = await commit.patchContent();
|
||||
await fs.writeFile(filename, content);
|
||||
commit.patchFile = filename;
|
||||
|
||||
const [, from] = /^From: ([^\n]+)$/im.exec(content) || [, null];
|
||||
const [, date] = /^Date: ([^\n]+)$/im.exec(content) || [, null];
|
||||
if (!from || !date) {
|
||||
throw new Error(`Commit ${commit.id} could not be parsed`);
|
||||
}
|
||||
if (!addresses.parseOneAddress(from)) {
|
||||
throw new Error(`Commit ${commit.id} author could not be parsed`);
|
||||
}
|
||||
commit.from = from;
|
||||
return commit;
|
||||
}),
|
||||
);
|
||||
|
||||
const git = simpleGit({ baseDir: process.cwd() });
|
||||
|
||||
// assuming that the repository in cwd is the desired destination
|
||||
await git.checkoutLocalBranch(destBranch);
|
||||
|
||||
for (let i = 0; i < patchFiles.length; i += 1) {
|
||||
const patch = patchFiles[i];
|
||||
await git.applyPatch(patch.patchFile, ['--index']);
|
||||
if (doNotCommit !== true) {
|
||||
await git.commit(patch.title + (patch.content ? '\n\n' + patch.content : ''), {
|
||||
'--author': patch.from,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -1184,6 +1184,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/email-addresses@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/email-addresses/-/email-addresses-3.0.0.tgz#c8c1ab4606ea7b320205faefbd65aea28ac0035c"
|
||||
integrity sha512-jGUOSgpOEWhTH4tMCj56NZenkzER259nJ5NGRvxXld3X7Lai/lxC3QNfDM0rVGMkj+WhANMpvIf195tgwnE7wQ==
|
||||
dependencies:
|
||||
email-addresses "*"
|
||||
|
||||
"@types/fs-extra@^9.0.6":
|
||||
version "9.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.6.tgz#488e56b77299899a608b8269719c1d133027a6ab"
|
||||
|
@ -2322,6 +2329,11 @@ elegant-spinner@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
|
||||
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
|
||||
|
||||
email-addresses@*, email-addresses@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-4.0.0.tgz#94fa214c30f943b02eaf91da717d89ff6a19e345"
|
||||
integrity sha512-Nas3sSSiD5lSIoqBos0FMjB9h4clHxXuAahHKGJ5doRWavEB7pBHzOxnI7R5f1MuGNrrSnsZFJ81HCBv0DZmnw==
|
||||
|
||||
"emoji-regex@>=6.0.0 <=6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e"
|
||||
|
|
Loading…
Reference in a new issue