diff --git a/README.md b/README.md index eb2c5de..5bdcc27 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ This tool comes with some inputs that allow users to override the default behavi | No Reviewers Inheritance | --no-inherit-reviewers | N | Considered only if reviewers is empty, if true keep reviewers as empty list, otherwise inherit from original pull request | false | | Backport Branch Names | --bp-branch-name | N | Comma separated lists of the backporting pull request branch names, if they exceeds 250 chars they will be truncated | bp-{target-branch}-{sha1}...{shaN} | | Backport Repository | --bp-repo | N | Optional source repository (format owner/repo) where the backport branch is pushed, useful to open the PR from a fork | {target-owner}/{target-repo} | +| Target Repository | --tb-repo | N | Optional target repository (format owner/repo) where the backport pull request should be opened against, useful to backport to a different repository than the original one | {original-pr-target-owner}/{original-pr-target-repo} | | Labels | --labels | N | Provide custom labels to be added to the backporting pull request | [] | | Inherit labels | --inherit-labels | N | If enabled inherit lables from the original pull request | false | | No squash | --no-squash | N | Backport all commits found in the pull request. The default behavior is to only backport the first commit that was merged in the base branch. | | @@ -161,6 +162,23 @@ $ git-backporting -tb v1 -pr https://github.com/upstream/project/pull/123 -a *** In this mode you should provide a PAT with enough permissions on the fork repository. +#### Backport to a different target repository + +By default, the backport pull request is opened against the same repository targeted by the original pull request. +If you want to open the backport PR against a different repository altogether, set `--tb-repo` (or action input `tb-repo`) to `owner/repo`. The repository is cloned from and the backport branch is pushed to `--tb-repo` instead of the original pull request's repository. + +```bash +$ git-backporting -tb v1 -pr https://github.com/upstream/project/pull/123 -a ***** --tb-repo my-org/downstream-project +``` + +`--tb-repo` and `--bp-repo` can be combined: `--tb-repo` selects where the backport PR is opened, while `--bp-repo` selects the fork the backport branch is pushed from. + +```bash +$ git-backporting -tb v1 -pr https://github.com/upstream/project/pull/123 -a ***** --tb-repo my-org/downstream-project --bp-repo my-user/downstream-project +``` + +When using `--tb-repo` alone, the backport branch is pushed directly to the target repository, so your PAT needs push access there. When combining it with `--bp-repo`, the branch is pushed to the fork instead, so your PAT needs push access on the fork and only the ability to open a pull request on the target repository. + #### Configuration file example This is an example of a configuration file that can be used. diff --git a/action.yml b/action.yml index 60f3ba7..628f371 100644 --- a/action.yml +++ b/action.yml @@ -64,6 +64,10 @@ inputs: description: > Optional backport repository as owner/repo where the backport branch is pushed, useful to create PRs from a fork required: false + tb-repo: + description: > + Optional target repository as owner/repo where the backport pull request should be opened against, useful to backport to a different repository than the original one + required: false reviewers: description: > Comma separated list of reviewers for the backporting pull request diff --git a/dist/cli/index.js b/dist/cli/index.js index cf88b0b..8f6d2d1 100755 --- a/dist/cli/index.js +++ b/dist/cli/index.js @@ -61,6 +61,7 @@ class ArgsParser { bodyPrefix: this.getOrDefault(args.bodyPrefix), bpBranchName: this.getOrDefault(args.bpBranchName), bpRepo: this.getOrDefault(args.bpRepo), + tbRepo: this.getOrDefault(args.tbRepo), reviewers: this.getOrDefault(args.reviewers, []), assignees: this.getOrDefault(args.assignees, []), inheritReviewers: this.getOrDefault(args.inheritReviewers, true), @@ -202,6 +203,7 @@ class CLIArgsParser extends args_parser_1.default { .option("--body-prefix ", "backport pr body prefix, default `backport `") .option("--bp-branch-name ", "comma separated list of backport pr branch names, default auto-generated by the commit and target branch") .option("--bp-repo ", "optional backport repository where the branch should be pushed, e.g. my-fork/my-repo") + .option("--tb-repo ", "optional target repository where the backport pull request should be opened against, e.g. my-org/my-repo") .option("--reviewers ", "comma separated list of reviewers for the backporting pull request", args_utils_1.getAsCleanedCommaSeparatedList) .option("--assignees ", "comma separated list of assignees for the backporting pull request", args_utils_1.getAsCleanedCommaSeparatedList) .option("--no-inherit-reviewers", "if provided and reviewers option is empty then inherit them from original pull request") @@ -241,6 +243,7 @@ class CLIArgsParser extends args_parser_1.default { bodyPrefix: opts.bodyPrefix, bpBranchName: opts.bpBranchName, bpRepo: opts.bpRepo, + tbRepo: opts.tbRepo, reviewers: opts.reviewers, assignees: opts.assignees, inheritReviewers: opts.inheritReviewers, @@ -427,7 +430,7 @@ class PullRequestConfigsParser extends configs_parser_1.default { * @returns {GitPullRequest} */ generateBackportPullRequestsData(originalPullRequest, args, targetBranches, bpBranchNames) { - const targetRepo = originalPullRequest.targetRepo; + const targetRepo = this.getBackportTargetRepo(args.tbRepo, originalPullRequest.targetRepo); const sourceRepo = this.getBackportSourceRepo(args.bpRepo, targetRepo); const reviewers = args.reviewers ?? []; if (reviewers.length == 0 && args.inheritReviewers) { @@ -466,6 +469,7 @@ class PullRequestConfigsParser extends configs_parser_1.default { return { owner: targetRepo.owner, repo: targetRepo.project, + cloneUrl: targetRepo.cloneUrl, head: backportBranch, headRepo: sourceRepo, base: tb, @@ -483,12 +487,29 @@ class PullRequestConfigsParser extends configs_parser_1.default { if (!bpRepo || bpRepo.trim() === "") { return undefined; } - const sanitized = bpRepo.trim(); + return this.parseRepo(bpRepo, "bp", targetRepo); + } + getBackportTargetRepo(tbRepo, targetRepo) { + if (!tbRepo || tbRepo.trim() === "") { + return targetRepo; + } + return this.parseRepo(tbRepo, "tb", targetRepo); + } + /** + * Parse a "owner/repo" formatted repository override and derive its clone url + * by reusing the scheme/host of the provided reference repository + * @param repo owner/repo formatted repository override + * @param optionName name of the option the override came from, used in the error message + * @param referenceRepo repository whose clone url is used to derive the scheme/host + * @returns {GitRepository} + */ + parseRepo(repo, optionName, referenceRepo) { + const sanitized = repo.trim(); const parts = sanitized.split("/").map(p => p.trim()).filter(p => p.length > 0); if (parts.length < 2) { - throw new Error(`Invalid bp repo format "${bpRepo}", expected "owner/repo"`); + throw new Error(`Invalid ${optionName} repo format "${repo}", expected "owner/repo"`); } - const cloneUrl = new URL(targetRepo.cloneUrl); + const cloneUrl = new URL(referenceRepo.cloneUrl); cloneUrl.pathname = `/${parts.join("/")}.git`; return { owner: parts[0], @@ -588,13 +609,20 @@ class GitCLIService { await this.git(cwd).checkoutLocalBranch(newBranch); } /** - * Add a new remote to the current repository + * Add a new remote to the current repository, or update its url if a remote + * with the same name already exists, e.g., because the working folder is + * reused across multiple backports * @param cwd repository in which addRemote should be performed * @param remote remote git link * @param remoteName [optional] name of the remote, by default 'fork' is used */ async addRemote(cwd, remote, remoteName = "fork") { this.logger.info(`Adding new remote ${remote}`); + const existingRemotes = await this.git(cwd).getRemotes(); + if (existingRemotes.some(r => r.name === remoteName)) { + await this.git(cwd).remote(["set-url", remoteName, this.remoteWithAuth(remote)]); + return; + } await this.git(cwd).addRemote(remoteName, this.remoteWithAuth(remote)); } /** @@ -1732,26 +1760,46 @@ class Runner { exports["default"] = Runner; function* backportSteps(logger, configs, backportPR, git) { // every failible operation should be in one dedicated closure + // whether the backport pr targets a different repository than the original pull request's one (--tb-repo), + // in which case the original pr's commits are not reachable from a clone of the backport target repo alone + const usingDifferentTargetRepo = backportPR.cloneUrl !== configs.originalPullRequest.targetRepo.cloneUrl; // 4. clone the repository yield async () => { logger.debug("Cloning repo.."); - await git.gitCli.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, backportPR.base); + await git.gitCli.clone(backportPR.cloneUrl, configs.folder, backportPR.base); }; // 5. create new branch from target one and checkout yield async () => { logger.debug("Creating local branch.."); await git.gitCli.createLocalBranch(configs.folder, backportPR.head); }; - // 6. fetch pull request remote if source owner != target owner or pull request still open - if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || + let commitsRemote = undefined; + if (usingDifferentTargetRepo) { + // 6. add a remote pointing to the original pull request's repository, needed to fetch + // commits that only exist there, since the backport target repo won't have them + commitsRemote = "upstream"; + yield async () => { + await git.gitCli.addRemote(configs.folder, configs.originalPullRequest.targetRepo.cloneUrl, commitsRemote); + }; + } + // 7. fetch pull request remote if source owner != target owner, pull request still open, + // or backporting to a different repository than the original pull request's one + if (usingDifferentTargetRepo || + configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || configs.originalPullRequest.state === "open") { yield async () => { logger.debug("Fetching pull request remote.."); const prefix = git.gitClientType === git_types_1.GitClientType.GITLAB ? "merge-requests" : "pull"; // default is for gitlab - await git.gitCli.fetch(configs.folder, `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`); + const ref = `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`; + if (commitsRemote) { + await git.gitCli.fetch(configs.folder, ref, commitsRemote); + } + else { + await git.gitCli.fetch(configs.folder, ref); + } }; } - // 7. apply all changes to the new branch + // 8. apply all changes to the new branch yield async () => { logger.debug("Cherry picking commits.."); }; @@ -1762,18 +1810,18 @@ function* backportSteps(logger, configs, backportPR, git) { } let target_remote = undefined; if (backportPR.headRepo) { - // 8. add fork-remote to push backport branch to + // 9. add fork-remote to push backport branch to target_remote = "fork"; yield async () => { await git.gitCli.addRemote(configs.folder, backportPR.headRepo.cloneUrl, target_remote); }; } if (!configs.dryRun) { - // 9. push the new branch to origin + // 10. push the new branch to origin yield async () => { await git.gitCli.push(configs.folder, backportPR.head, target_remote); }; - // 10. create pull request new branch -> target branch (using octokit) + // 11. create pull request new branch -> target branch (using octokit) yield async () => { const prUrl = await git.gitClientApi.createPullRequest(backportPR); logger.info(`Pull request created: ${prUrl}`); diff --git a/dist/gha/index.js b/dist/gha/index.js index 1632e33..db46c4e 100755 --- a/dist/gha/index.js +++ b/dist/gha/index.js @@ -61,6 +61,7 @@ class ArgsParser { bodyPrefix: this.getOrDefault(args.bodyPrefix), bpBranchName: this.getOrDefault(args.bpBranchName), bpRepo: this.getOrDefault(args.bpRepo), + tbRepo: this.getOrDefault(args.tbRepo), reviewers: this.getOrDefault(args.reviewers, []), assignees: this.getOrDefault(args.assignees, []), inheritReviewers: this.getOrDefault(args.inheritReviewers, true), @@ -205,6 +206,7 @@ class GHAArgsParser extends args_parser_1.default { bodyPrefix: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("body-prefix", { trimWhitespace: false })), bpBranchName: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("bp-branch-name")), bpRepo: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("bp-repo")), + tbRepo: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("tb-repo")), reviewers: (0, args_utils_1.getAsCleanedCommaSeparatedList)((0, core_1.getInput)("reviewers")), assignees: (0, args_utils_1.getAsCleanedCommaSeparatedList)((0, core_1.getInput)("assignees")), inheritReviewers: !(0, args_utils_1.getAsBooleanOrUndefined)((0, core_1.getInput)("no-inherit-reviewers")), @@ -391,7 +393,7 @@ class PullRequestConfigsParser extends configs_parser_1.default { * @returns {GitPullRequest} */ generateBackportPullRequestsData(originalPullRequest, args, targetBranches, bpBranchNames) { - const targetRepo = originalPullRequest.targetRepo; + const targetRepo = this.getBackportTargetRepo(args.tbRepo, originalPullRequest.targetRepo); const sourceRepo = this.getBackportSourceRepo(args.bpRepo, targetRepo); const reviewers = args.reviewers ?? []; if (reviewers.length == 0 && args.inheritReviewers) { @@ -430,6 +432,7 @@ class PullRequestConfigsParser extends configs_parser_1.default { return { owner: targetRepo.owner, repo: targetRepo.project, + cloneUrl: targetRepo.cloneUrl, head: backportBranch, headRepo: sourceRepo, base: tb, @@ -447,12 +450,29 @@ class PullRequestConfigsParser extends configs_parser_1.default { if (!bpRepo || bpRepo.trim() === "") { return undefined; } - const sanitized = bpRepo.trim(); + return this.parseRepo(bpRepo, "bp", targetRepo); + } + getBackportTargetRepo(tbRepo, targetRepo) { + if (!tbRepo || tbRepo.trim() === "") { + return targetRepo; + } + return this.parseRepo(tbRepo, "tb", targetRepo); + } + /** + * Parse a "owner/repo" formatted repository override and derive its clone url + * by reusing the scheme/host of the provided reference repository + * @param repo owner/repo formatted repository override + * @param optionName name of the option the override came from, used in the error message + * @param referenceRepo repository whose clone url is used to derive the scheme/host + * @returns {GitRepository} + */ + parseRepo(repo, optionName, referenceRepo) { + const sanitized = repo.trim(); const parts = sanitized.split("/").map(p => p.trim()).filter(p => p.length > 0); if (parts.length < 2) { - throw new Error(`Invalid bp repo format "${bpRepo}", expected "owner/repo"`); + throw new Error(`Invalid ${optionName} repo format "${repo}", expected "owner/repo"`); } - const cloneUrl = new URL(targetRepo.cloneUrl); + const cloneUrl = new URL(referenceRepo.cloneUrl); cloneUrl.pathname = `/${parts.join("/")}.git`; return { owner: parts[0], @@ -552,13 +572,20 @@ class GitCLIService { await this.git(cwd).checkoutLocalBranch(newBranch); } /** - * Add a new remote to the current repository + * Add a new remote to the current repository, or update its url if a remote + * with the same name already exists, e.g., because the working folder is + * reused across multiple backports * @param cwd repository in which addRemote should be performed * @param remote remote git link * @param remoteName [optional] name of the remote, by default 'fork' is used */ async addRemote(cwd, remote, remoteName = "fork") { this.logger.info(`Adding new remote ${remote}`); + const existingRemotes = await this.git(cwd).getRemotes(); + if (existingRemotes.some(r => r.name === remoteName)) { + await this.git(cwd).remote(["set-url", remoteName, this.remoteWithAuth(remote)]); + return; + } await this.git(cwd).addRemote(remoteName, this.remoteWithAuth(remote)); } /** @@ -1696,26 +1723,46 @@ class Runner { exports["default"] = Runner; function* backportSteps(logger, configs, backportPR, git) { // every failible operation should be in one dedicated closure + // whether the backport pr targets a different repository than the original pull request's one (--tb-repo), + // in which case the original pr's commits are not reachable from a clone of the backport target repo alone + const usingDifferentTargetRepo = backportPR.cloneUrl !== configs.originalPullRequest.targetRepo.cloneUrl; // 4. clone the repository yield async () => { logger.debug("Cloning repo.."); - await git.gitCli.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, backportPR.base); + await git.gitCli.clone(backportPR.cloneUrl, configs.folder, backportPR.base); }; // 5. create new branch from target one and checkout yield async () => { logger.debug("Creating local branch.."); await git.gitCli.createLocalBranch(configs.folder, backportPR.head); }; - // 6. fetch pull request remote if source owner != target owner or pull request still open - if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || + let commitsRemote = undefined; + if (usingDifferentTargetRepo) { + // 6. add a remote pointing to the original pull request's repository, needed to fetch + // commits that only exist there, since the backport target repo won't have them + commitsRemote = "upstream"; + yield async () => { + await git.gitCli.addRemote(configs.folder, configs.originalPullRequest.targetRepo.cloneUrl, commitsRemote); + }; + } + // 7. fetch pull request remote if source owner != target owner, pull request still open, + // or backporting to a different repository than the original pull request's one + if (usingDifferentTargetRepo || + configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || configs.originalPullRequest.state === "open") { yield async () => { logger.debug("Fetching pull request remote.."); const prefix = git.gitClientType === git_types_1.GitClientType.GITLAB ? "merge-requests" : "pull"; // default is for gitlab - await git.gitCli.fetch(configs.folder, `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`); + const ref = `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`; + if (commitsRemote) { + await git.gitCli.fetch(configs.folder, ref, commitsRemote); + } + else { + await git.gitCli.fetch(configs.folder, ref); + } }; } - // 7. apply all changes to the new branch + // 8. apply all changes to the new branch yield async () => { logger.debug("Cherry picking commits.."); }; @@ -1726,18 +1773,18 @@ function* backportSteps(logger, configs, backportPR, git) { } let target_remote = undefined; if (backportPR.headRepo) { - // 8. add fork-remote to push backport branch to + // 9. add fork-remote to push backport branch to target_remote = "fork"; yield async () => { await git.gitCli.addRemote(configs.folder, backportPR.headRepo.cloneUrl, target_remote); }; } if (!configs.dryRun) { - // 9. push the new branch to origin + // 10. push the new branch to origin yield async () => { await git.gitCli.push(configs.folder, backportPR.head, target_remote); }; - // 10. create pull request new branch -> target branch (using octokit) + // 11. create pull request new branch -> target branch (using octokit) yield async () => { const prUrl = await git.gitClientApi.createPullRequest(backportPR); logger.info(`Pull request created: ${prUrl}`); diff --git a/src/service/args/args-parser.ts b/src/service/args/args-parser.ts index d9c4d28..cf30377 100644 --- a/src/service/args/args-parser.ts +++ b/src/service/args/args-parser.ts @@ -39,6 +39,7 @@ export default abstract class ArgsParser { bodyPrefix: this.getOrDefault(args.bodyPrefix), bpBranchName: this.getOrDefault(args.bpBranchName), bpRepo: this.getOrDefault(args.bpRepo), + tbRepo: this.getOrDefault(args.tbRepo), reviewers: this.getOrDefault(args.reviewers, []), assignees: this.getOrDefault(args.assignees, []), inheritReviewers: this.getOrDefault(args.inheritReviewers, true), diff --git a/src/service/args/args.types.ts b/src/service/args/args.types.ts index 82e7ae8..68145b6 100644 --- a/src/service/args/args.types.ts +++ b/src/service/args/args.types.ts @@ -18,6 +18,7 @@ export interface Args { // NOTE: keep bpBranchName as singular and of type string for backward compatibilities bpBranchName?: string, // comma separated list of backport pr branch names, default computed from commit and target branches bpRepo?: string, // optional backport repository as owner/repo where the branch will be pushed + tbRepo?: string, // optional target repository as owner/repo where the backport pr should be opened against reviewers?: string[], // backport pr reviewers assignees?: string[], // backport pr assignees inheritReviewers?: boolean, // if true and reviewers == [] then inherit reviewers from original pr diff --git a/src/service/args/cli/cli-args-parser.ts b/src/service/args/cli/cli-args-parser.ts index fbaaceb..2374981 100644 --- a/src/service/args/cli/cli-args-parser.ts +++ b/src/service/args/cli/cli-args-parser.ts @@ -24,6 +24,7 @@ export default class CLIArgsParser extends ArgsParser { .option("--body-prefix ", "backport pr body prefix, default `backport `") .option("--bp-branch-name ", "comma separated list of backport pr branch names, default auto-generated by the commit and target branch") .option("--bp-repo ", "optional backport repository where the branch should be pushed, e.g. my-fork/my-repo") + .option("--tb-repo ", "optional target repository where the backport pull request should be opened against, e.g. my-org/my-repo") .option("--reviewers ", "comma separated list of reviewers for the backporting pull request", getAsCleanedCommaSeparatedList) .option("--assignees ", "comma separated list of assignees for the backporting pull request", getAsCleanedCommaSeparatedList) .option("--no-inherit-reviewers", "if provided and reviewers option is empty then inherit them from original pull request") @@ -64,6 +65,7 @@ export default class CLIArgsParser extends ArgsParser { bodyPrefix: opts.bodyPrefix, bpBranchName: opts.bpBranchName, bpRepo: opts.bpRepo, + tbRepo: opts.tbRepo, reviewers: opts.reviewers, assignees: opts.assignees, inheritReviewers: opts.inheritReviewers, diff --git a/src/service/args/gha/gha-args-parser.ts b/src/service/args/gha/gha-args-parser.ts index 79053b2..f3892bd 100644 --- a/src/service/args/gha/gha-args-parser.ts +++ b/src/service/args/gha/gha-args-parser.ts @@ -27,6 +27,7 @@ export default class GHAArgsParser extends ArgsParser { bodyPrefix: getOrUndefined(getInput("body-prefix", { trimWhitespace: false })), bpBranchName: getOrUndefined(getInput("bp-branch-name")), bpRepo: getOrUndefined(getInput("bp-repo")), + tbRepo: getOrUndefined(getInput("tb-repo")), reviewers: getAsCleanedCommaSeparatedList(getInput("reviewers")), assignees: getAsCleanedCommaSeparatedList(getInput("assignees")), inheritReviewers: !getAsBooleanOrUndefined(getInput("no-inherit-reviewers")), diff --git a/src/service/configs/pullrequest/pr-configs-parser.ts b/src/service/configs/pullrequest/pr-configs-parser.ts index 72da3cc..4a8f2f1 100644 --- a/src/service/configs/pullrequest/pr-configs-parser.ts +++ b/src/service/configs/pullrequest/pr-configs-parser.ts @@ -116,7 +116,7 @@ export default class PullRequestConfigsParser extends ConfigsParser { bpBranchNames: string[] ): BackportPullRequest[] { - const targetRepo = originalPullRequest.targetRepo; + const targetRepo = this.getBackportTargetRepo(args.tbRepo, originalPullRequest.targetRepo); const sourceRepo = this.getBackportSourceRepo(args.bpRepo, targetRepo); const reviewers = args.reviewers ?? []; @@ -161,6 +161,7 @@ export default class PullRequestConfigsParser extends ConfigsParser { return { owner: targetRepo.owner, repo: targetRepo.project, + cloneUrl: targetRepo.cloneUrl, head: backportBranch, headRepo: sourceRepo, base: tb, @@ -180,13 +181,33 @@ export default class PullRequestConfigsParser extends ConfigsParser { return undefined; } - const sanitized = bpRepo.trim(); + return this.parseRepo(bpRepo, "bp", targetRepo); + } + + private getBackportTargetRepo(tbRepo: string | undefined, targetRepo: GitRepository): GitRepository { + if (!tbRepo || tbRepo.trim() === "") { + return targetRepo; + } + + return this.parseRepo(tbRepo, "tb", targetRepo); + } + + /** + * Parse a "owner/repo" formatted repository override and derive its clone url + * by reusing the scheme/host of the provided reference repository + * @param repo owner/repo formatted repository override + * @param optionName name of the option the override came from, used in the error message + * @param referenceRepo repository whose clone url is used to derive the scheme/host + * @returns {GitRepository} + */ + private parseRepo(repo: string, optionName: string, referenceRepo: GitRepository): GitRepository { + const sanitized = repo.trim(); const parts = sanitized.split("/").map(p => p.trim()).filter(p => p.length > 0); if (parts.length < 2) { - throw new Error(`Invalid bp repo format "${bpRepo}", expected "owner/repo"`); + throw new Error(`Invalid ${optionName} repo format "${repo}", expected "owner/repo"`); } - const cloneUrl = new URL(targetRepo.cloneUrl); + const cloneUrl = new URL(referenceRepo.cloneUrl); cloneUrl.pathname = `/${parts.join("/")}.git`; return { diff --git a/src/service/git/git-cli.ts b/src/service/git/git-cli.ts index 02d51e3..dc82ef5 100644 --- a/src/service/git/git-cli.ts +++ b/src/service/git/git-cli.ts @@ -93,13 +93,21 @@ export default class GitCLIService { } /** - * Add a new remote to the current repository + * Add a new remote to the current repository, or update its url if a remote + * with the same name already exists, e.g., because the working folder is + * reused across multiple backports * @param cwd repository in which addRemote should be performed * @param remote remote git link * @param remoteName [optional] name of the remote, by default 'fork' is used */ async addRemote(cwd: string, remote: string, remoteName = "fork"): Promise { this.logger.info(`Adding new remote ${remote}`); + const existingRemotes = await this.git(cwd).getRemotes(); + if (existingRemotes.some(r => r.name === remoteName)) { + await this.git(cwd).remote(["set-url", remoteName, this.remoteWithAuth(remote)]); + return; + } + await this.git(cwd).addRemote(remoteName, this.remoteWithAuth(remote)); } diff --git a/src/service/git/git.types.ts b/src/service/git/git.types.ts index 965e097..2a0bfdc 100644 --- a/src/service/git/git.types.ts +++ b/src/service/git/git.types.ts @@ -27,6 +27,7 @@ export interface GitRepository { export interface BackportPullRequest { owner: string, // repository's owner repo: string, // repository's name + cloneUrl: string, // clone url of the target repository (owner/repo above) head: string, // name of the source branch headRepo?: GitRepository, // optional: source repository, for cross-repository pull requests base: string, // name of the target branch diff --git a/src/service/runner/runner.ts b/src/service/runner/runner.ts index 0ab367d..df917d7 100644 --- a/src/service/runner/runner.ts +++ b/src/service/runner/runner.ts @@ -156,10 +156,14 @@ export default class Runner { function* backportSteps(logger: Pick, configs: Configs, backportPR: BackportPullRequest, git: Git): Generator<() => Promise, void, unknown> { // every failible operation should be in one dedicated closure + // whether the backport pr targets a different repository than the original pull request's one (--tb-repo), + // in which case the original pr's commits are not reachable from a clone of the backport target repo alone + const usingDifferentTargetRepo = backportPR.cloneUrl !== configs.originalPullRequest.targetRepo.cloneUrl; + // 4. clone the repository yield async () => { logger.debug("Cloning repo.."); - await git.gitCli.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, backportPR.base); + await git.gitCli.clone(backportPR.cloneUrl, configs.folder, backportPR.base); }; // 5. create new branch from target one and checkout @@ -168,17 +172,34 @@ function* backportSteps(logger: Pick, await git.gitCli.createLocalBranch(configs.folder, backportPR.head); }; - // 6. fetch pull request remote if source owner != target owner or pull request still open - if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || + let commitsRemote: string | undefined = undefined; + if (usingDifferentTargetRepo) { + // 6. add a remote pointing to the original pull request's repository, needed to fetch + // commits that only exist there, since the backport target repo won't have them + commitsRemote = "upstream"; + yield async () => { + await git.gitCli.addRemote(configs.folder, configs.originalPullRequest.targetRepo.cloneUrl, commitsRemote); + }; + } + + // 7. fetch pull request remote if source owner != target owner, pull request still open, + // or backporting to a different repository than the original pull request's one + if (usingDifferentTargetRepo || + configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || configs.originalPullRequest.state === "open") { yield async () => { logger.debug("Fetching pull request remote.."); const prefix = git.gitClientType === GitClientType.GITLAB ? "merge-requests" : "pull"; // default is for gitlab - await git.gitCli.fetch(configs.folder, `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`); + const ref = `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`; + if (commitsRemote) { + await git.gitCli.fetch(configs.folder, ref, commitsRemote); + } else { + await git.gitCli.fetch(configs.folder, ref); + } }; } - // 7. apply all changes to the new branch + // 8. apply all changes to the new branch yield async () => { logger.debug("Cherry picking commits.."); }; @@ -191,7 +212,7 @@ function* backportSteps(logger: Pick, let target_remote: string | undefined = undefined; if (backportPR.headRepo) { - // 8. add fork-remote to push backport branch to + // 9. add fork-remote to push backport branch to target_remote = "fork"; yield async () => { await git.gitCli.addRemote(configs.folder, backportPR.headRepo!.cloneUrl, target_remote); @@ -199,12 +220,12 @@ function* backportSteps(logger: Pick, } if (!configs.dryRun) { - // 9. push the new branch to origin + // 10. push the new branch to origin yield async () => { await git.gitCli.push(configs.folder, backportPR.head, target_remote); }; - // 10. create pull request new branch -> target branch (using octokit) + // 11. create pull request new branch -> target branch (using octokit) yield async () => { const prUrl = await git.gitClientApi.createPullRequest(backportPR); logger.info(`Pull request created: ${prUrl}`); diff --git a/test/service/args/cli/cli-args-parser.test.ts b/test/service/args/cli/cli-args-parser.test.ts index ff35305..a964648 100644 --- a/test/service/args/cli/cli-args-parser.test.ts +++ b/test/service/args/cli/cli-args-parser.test.ts @@ -244,6 +244,8 @@ describe("cli args parser", () => { "bp_branch_name", "--bp-repo", "fork-user/reponame", + "--tb-repo", + "target-org/reponame", "--reviewers", "al , john, jack", "--assignees", @@ -268,6 +270,7 @@ describe("cli args parser", () => { expect(args.bodyPrefix).toEqual("New Body Prefix"); expect(args.bpBranchName).toEqual("bp_branch_name"); expect(args.bpRepo).toEqual("fork-user/reponame"); + expect(args.tbRepo).toEqual("target-org/reponame"); expectArrayEqual(args.reviewers!, ["al", "john", "jack"]); expectArrayEqual(args.assignees!, ["pippo", "pluto", "paperino"]); expect(args.inheritReviewers).toEqual(false); diff --git a/test/service/args/gha/gha-args-parser.test.ts b/test/service/args/gha/gha-args-parser.test.ts index 0469aa0..0744e67 100644 --- a/test/service/args/gha/gha-args-parser.test.ts +++ b/test/service/args/gha/gha-args-parser.test.ts @@ -88,6 +88,7 @@ describe("gha args parser", () => { "body-prefix": "New Body Prefix", "bp-branch-name": "bp_branch_name", "bp-repo": "fork-user/reponame", + "tb-repo": "target-org/reponame", "reviewers": "al , john, jack", "assignees": " pippo,pluto, paperino", "no-inherit-reviewers": "true", @@ -108,6 +109,7 @@ describe("gha args parser", () => { expect(args.bodyPrefix).toEqual("New Body Prefix"); expect(args.bpBranchName).toEqual("bp_branch_name"); expect(args.bpRepo).toEqual("fork-user/reponame"); + expect(args.tbRepo).toEqual("target-org/reponame"); expectArrayEqual(args.reviewers!, ["al", "john", "jack"]); expectArrayEqual(args.assignees!, ["pippo", "pluto", "paperino"]); expect(args.inheritReviewers).toEqual(false); diff --git a/test/service/configs/pullrequest/github-pr-configs-parser-multiple.test.ts b/test/service/configs/pullrequest/github-pr-configs-parser-multiple.test.ts index 2141633..f4dbe41 100644 --- a/test/service/configs/pullrequest/github-pr-configs-parser-multiple.test.ts +++ b/test/service/configs/pullrequest/github-pr-configs-parser-multiple.test.ts @@ -70,6 +70,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v1-28f63db", base: "v1", title: "New Title", @@ -82,6 +83,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v2-28f63db", base: "v2", title: "New Title", @@ -94,6 +96,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v3-28f63db", base: "v3", title: "New Title", @@ -142,6 +145,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v1-28f63db", base: "v1", title: "New Title", @@ -154,6 +158,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v2-28f63db", base: "v2", title: "New Title", @@ -166,6 +171,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v3-28f63db", base: "v3", title: "New Title", @@ -215,6 +221,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-branch-v1", base: "v1", title: "New Title", @@ -227,6 +234,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-branch-v2", base: "v2", title: "New Title", @@ -239,6 +247,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-branch-v3", base: "v3", title: "New Title", @@ -288,6 +297,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-branch1", base: "v1", title: "New Title", @@ -300,6 +310,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-branch2", base: "v2", title: "New Title", @@ -312,6 +323,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-branch3", base: "v3", title: "New Title", @@ -382,6 +394,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v4-0404fb9-11da4e3", base: "v4", title: "[v4] PR Title", @@ -394,6 +407,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v5-0404fb9-11da4e3", base: "v5", title: "[v5] PR Title", @@ -406,6 +420,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v6-0404fb9-11da4e3", base: "v6", title: "[v6] PR Title", @@ -453,6 +468,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v1-0404fb9-11da4e3", base: "v1", title: "[v1] PR Title", @@ -465,6 +481,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v2-0404fb9-11da4e3", base: "v2", title: "[v2] PR Title", @@ -477,6 +494,7 @@ describe("github pull request config parser", () => { { owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v3-0404fb9-11da4e3", base: "v3", title: "[v3] PR Title", diff --git a/test/service/configs/pullrequest/github-pr-configs-parser.test.ts b/test/service/configs/pullrequest/github-pr-configs-parser.test.ts index ba83b50..207272e 100644 --- a/test/service/configs/pullrequest/github-pr-configs-parser.test.ts +++ b/test/service/configs/pullrequest/github-pr-configs-parser.test.ts @@ -130,6 +130,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "[prod] PR Title", @@ -303,6 +304,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-branch", base: "prod", title: "New Title", @@ -345,6 +347,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "New Title", @@ -417,6 +420,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "New Title", @@ -489,6 +493,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "New Title", @@ -563,6 +568,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "New Title", @@ -593,6 +599,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", headRepo: { cloneUrl: "https://github.com/my-fork/reponame.git", @@ -609,6 +616,91 @@ describe("github pull request config parser", () => { }); }); + test("override backport target repository", async () => { + const args: Args = { + dryRun: false, + auth: "", + pullRequest: mergedPRUrl, + targetBranch: "prod", + tbRepo: "target-org/reponame", + gitUser: "Me", + gitEmail: "me@email.com", + reviewers: [], + assignees: [], + inheritReviewers: false, + }; + + const configs: Configs = await configParser.parseAndValidate(args); + + expect(configs.backportPullRequests[0]).toEqual({ + owner: "target-org", + repo: "reponame", + cloneUrl: "https://github.com/target-org/reponame.git", + head: "bp-prod-28f63db", + base: "prod", + title: "[prod] PR Title", + body: "**Backport:** https://github.com/owner/reponame/pull/2368\r\n\r\nPlease review and merge", + reviewers: [], + assignees: [], + labels: [], + comments: [], + }); + }); + + test("override backport target and source repository", async () => { + const args: Args = { + dryRun: false, + auth: "", + pullRequest: mergedPRUrl, + targetBranch: "prod", + tbRepo: "target-org/reponame", + bpRepo: "my-fork/reponame", + gitUser: "Me", + gitEmail: "me@email.com", + reviewers: [], + assignees: [], + inheritReviewers: false, + }; + + const configs: Configs = await configParser.parseAndValidate(args); + + expect(configs.backportPullRequests[0]).toEqual({ + owner: "target-org", + repo: "reponame", + cloneUrl: "https://github.com/target-org/reponame.git", + head: "bp-prod-28f63db", + headRepo: { + cloneUrl: "https://github.com/my-fork/reponame.git", + owner: "my-fork", + project: "reponame", + }, + base: "prod", + title: "[prod] PR Title", + body: "**Backport:** https://github.com/owner/reponame/pull/2368\r\n\r\nPlease review and merge", + reviewers: [], + assignees: [], + labels: [], + comments: [], + }); + }); + + test("invalid tb repo format", async () => { + const args: Args = { + dryRun: false, + auth: "", + pullRequest: mergedPRUrl, + targetBranch: "prod", + tbRepo: "invalid-format", + gitUser: "Me", + gitEmail: "me@email.com", + reviewers: [], + assignees: [], + inheritReviewers: false, + }; + + await expect(() => configParser.parseAndValidate(args)).rejects.toThrow("Invalid tb repo format \"invalid-format\", expected \"owner/repo\""); + }); + test("using simple config file", async () => { addProcessArgs([ "-cf", @@ -660,6 +752,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "[prod] PR Title", @@ -723,6 +816,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "New Title", @@ -796,6 +890,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-0404fb9-11da4e3", base: "prod", title: "[prod] PR Title", @@ -871,6 +966,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "New Title", @@ -968,6 +1064,7 @@ describe("github pull request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-prod-28f63db", base: "prod", title: "New Title", diff --git a/test/service/configs/pullrequest/gitlab-pr-configs-parser-multiple.test.ts b/test/service/configs/pullrequest/gitlab-pr-configs-parser-multiple.test.ts index 343f87d..fb015a7 100644 --- a/test/service/configs/pullrequest/gitlab-pr-configs-parser-multiple.test.ts +++ b/test/service/configs/pullrequest/gitlab-pr-configs-parser-multiple.test.ts @@ -70,6 +70,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-v1-ebb1eca", base: "v1", title: "New Title", @@ -82,6 +83,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-v2-ebb1eca", base: "v2", title: "New Title", @@ -94,6 +96,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-v3-ebb1eca", base: "v3", title: "New Title", @@ -143,6 +146,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-v1-ebb1eca", base: "v1", title: "New Title", @@ -155,6 +159,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-v2-ebb1eca", base: "v2", title: "New Title", @@ -167,6 +172,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-v3-ebb1eca", base: "v3", title: "New Title", @@ -217,6 +223,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "custom-branch-v1", base: "v1", title: "New Title", @@ -229,6 +236,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "custom-branch-v2", base: "v2", title: "New Title", @@ -241,6 +249,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "custom-branch-v3", base: "v3", title: "New Title", @@ -291,6 +300,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "custom1", base: "v1", title: "New Title", @@ -303,6 +313,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "custom2", base: "v2", title: "New Title", @@ -315,6 +326,7 @@ describe("gitlab merge request config parser", () => { { owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "custom3", base: "v3", title: "New Title", diff --git a/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts b/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts index d2ffa14..04246a7 100644 --- a/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts +++ b/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts @@ -135,6 +135,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "[prod] Update test.txt", @@ -317,6 +318,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -389,6 +391,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -461,6 +464,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -535,6 +539,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -596,6 +601,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "[prod] Update test.txt", @@ -657,6 +663,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -726,6 +733,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-e4dd336-974519f", base: "prod", title: "[prod] Update test.txt opened", @@ -801,6 +809,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -876,6 +885,7 @@ describe("gitlab merge request config parser", () => { expect(configs.backportPullRequests[0]).toEqual({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", diff --git a/test/service/git/git-cli.test.ts b/test/service/git/git-cli.test.ts index 9497b32..65410b3 100644 --- a/test/service/git/git-cli.test.ts +++ b/test/service/git/git-cli.test.ts @@ -124,6 +124,14 @@ describe("git cli service", () => { expect(post).toEqual("tbranch"); }); + test("add remote twice updates the url instead of throwing", async () => { + await git.addRemote(cwd, "https://example.com/first/repo.git", "upstream"); + await expect(git.addRemote(cwd, "https://example.com/second/repo.git", "upstream")).resolves.not.toThrow(); + + const remoteURL = spawnSync("git", ["remote", "get-url", "upstream"], { cwd }).stdout.toString().trim(); + expect(remoteURL).toEqual("https://example.com/second/repo.git"); + }); + test("git clone set url with auth correctly for API token", async () => { const git2 = new GitCLIService("api-token", { user: "Backporting bot", diff --git a/test/service/git/gitlab/gitlab-client.test.ts b/test/service/git/gitlab/gitlab-client.test.ts index 2799ab1..22f0a34 100644 --- a/test/service/git/gitlab/gitlab-client.test.ts +++ b/test/service/git/gitlab/gitlab-client.test.ts @@ -84,6 +84,7 @@ describe("github service", () => { body: "Backport Body", owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", base: "old/branch", head: "bp-branch", reviewers: [], @@ -115,6 +116,7 @@ describe("github service", () => { body: "Backport Body", owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", base: "old/branch", head: "bp-branch", reviewers: ["superuser", "invalid"], @@ -151,6 +153,7 @@ describe("github service", () => { body: "Backport Body", owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", base: "old/branch", head: "bp-branch", reviewers: [], @@ -187,6 +190,7 @@ describe("github service", () => { body: "Backport Body", owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", base: "old/branch", head: "bp-branch-2", reviewers: ["superuser", "invalid"], @@ -223,6 +227,7 @@ describe("github service", () => { body: "Backport Body", owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", base: "old/branch", head: "bp-branch-2", reviewers: [], @@ -259,6 +264,7 @@ describe("github service", () => { body: "Backport Body", owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", base: "old/branch", head: "bp-branch-2", reviewers: [], @@ -293,6 +299,7 @@ describe("github service", () => { body: "Backport Body", owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", base: "old/branch", head: "bp-branch-2", reviewers: [], diff --git a/test/service/runner/cli-codeberg-runner.test.ts b/test/service/runner/cli-codeberg-runner.test.ts index 3115de8..fe1ebce 100644 --- a/test/service/runner/cli-codeberg-runner.test.ts +++ b/test/service/runner/cli-codeberg-runner.test.ts @@ -232,6 +232,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -278,6 +279,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -337,6 +339,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-9174896", base: "target", title: "[target] PR Title", @@ -386,6 +389,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-11da4e3-0404fb9", base: "target", title: "[target] PR Title", @@ -445,6 +449,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -503,6 +508,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -553,6 +559,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -602,6 +609,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -647,6 +655,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -695,6 +704,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -743,6 +753,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-11da4e3-0404fb9", base: "target", title: "[target] PR Title", @@ -797,6 +808,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: truncatedBranch, base: "target", title: "[target] PR Title", @@ -849,6 +861,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-11da4e3-0404fb9", base: "target", title: "[target] PR Title", @@ -899,6 +912,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -956,6 +970,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-v1-28f63db", base: "v1", title: "[v1] PR Title", @@ -968,6 +983,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-v2-28f63db", base: "v2", title: "[v2] PR Title", @@ -980,6 +996,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "bp-v3-28f63db", base: "v3", title: "[v3] PR Title", @@ -1038,6 +1055,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom1", base: "v1", title: "[v1] PR Title", @@ -1050,6 +1068,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom2", base: "v2", title: "[v2] PR Title", @@ -1062,6 +1081,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom3", base: "v3", title: "[v3] PR Title", @@ -1124,6 +1144,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom-failure-head-v1", base: "v1", title: "[v1] PR Title", @@ -1136,6 +1157,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom-failure-head-v2", base: "v2", title: "[v2] PR Title", @@ -1148,6 +1170,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom-failure-head-v3", base: "v3", title: "[v3] PR Title", @@ -1295,6 +1318,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom-failure-head-v1", base: "v1", title: "[v1] PR Title", @@ -1307,6 +1331,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://codeberg.org/owner/reponame.git", head: "custom-failure-head-v2", base: "v2", title: "[v2] PR Title", diff --git a/test/service/runner/cli-github-runner.test.ts b/test/service/runner/cli-github-runner.test.ts index e55974b..87339af 100644 --- a/test/service/runner/cli-github-runner.test.ts +++ b/test/service/runner/cli-github-runner.test.ts @@ -232,6 +232,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -269,6 +270,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", headRepo: { cloneUrl: "https://github.com/my-fork/reponame.git", @@ -286,6 +288,43 @@ describe("cli runner", () => { ); }); + test("without dry run using target backport repo", async () => { + addProcessArgs([ + "-tb", + "target", + "-pr", + "https://github.com/owner/reponame/pull/2368", + "--tb-repo", + "target-org/reponame", + ]); + + await runner.execute(); + + const cwd = process.cwd() + "/bp"; + + expect(GitCLIService.prototype.clone).toHaveBeenCalledTimes(1); + expect(GitCLIService.prototype.clone).toHaveBeenCalledWith("https://github.com/target-org/reponame.git", cwd, "target"); + + expect(GitCLIService.prototype.push).toHaveBeenCalledTimes(1); + expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "bp-target-28f63db", undefined); + + expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledTimes(1); + expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ + owner: "target-org", + repo: "reponame", + cloneUrl: "https://github.com/target-org/reponame.git", + head: "bp-target-28f63db", + base: "target", + title: "[target] PR Title", + body: "**Backport:** https://github.com/owner/reponame/pull/2368\r\n\r\nPlease review and merge", + reviewers: ["gh-user", "that-s-a-user"], + assignees: [], + labels: [], + comments: [], + } + ); + }); + test("same owner", async () => { addProcessArgs([ "-tb", @@ -319,6 +358,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -378,6 +418,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-9174896", base: "target", title: "[target] PR Title", @@ -427,6 +468,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-0404fb9-11da4e3", base: "target", title: "[target] PR Title", @@ -486,6 +528,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -544,6 +587,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -594,6 +638,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -643,6 +688,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -688,6 +734,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -736,6 +783,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -784,6 +832,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-0404fb9-11da4e3", base: "target", title: "[target] PR Title", @@ -838,6 +887,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: truncatedBranch, base: "target", title: "[target] PR Title", @@ -890,6 +940,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-0404fb9-11da4e3", base: "target", title: "[target] PR Title", @@ -940,6 +991,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -997,6 +1049,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v1-28f63db", base: "v1", title: "[v1] PR Title", @@ -1009,6 +1062,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v2-28f63db", base: "v2", title: "[v2] PR Title", @@ -1021,6 +1075,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v3-28f63db", base: "v3", title: "[v3] PR Title", @@ -1033,6 +1088,61 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveReturnedTimes(3); }); + test("with multiple target branches using target backport repo", async () => { + addProcessArgs([ + "-tb", + "v1, v2, v3", + "-pr", + "https://github.com/owner/reponame/pull/2368", + "--tb-repo", + "target-org/reponame", + "-f", + "/tmp/folder" + ]); + + await runner.execute(); + + const cwd = "/tmp/folder"; + + expect(GitCLIService.prototype.clone).toHaveBeenCalledTimes(3); + expect(GitCLIService.prototype.clone).toHaveBeenCalledWith("https://github.com/target-org/reponame.git", cwd, "v1"); + expect(GitCLIService.prototype.clone).toHaveBeenCalledWith("https://github.com/target-org/reponame.git", cwd, "v2"); + expect(GitCLIService.prototype.clone).toHaveBeenCalledWith("https://github.com/target-org/reponame.git", cwd, "v3"); + + // the "upstream" remote (pointing back at the original pr's repo) must be (re)added on every + // iteration since the working folder is reused across target branches + expect(GitCLIService.prototype.addRemote).toHaveBeenCalledTimes(3); + expect(GitCLIService.prototype.addRemote).toHaveBeenCalledWith(cwd, "https://github.com/owner/reponame.git", "upstream"); + + expect(GitCLIService.prototype.fetch).toHaveBeenCalledTimes(3); + expect(GitCLIService.prototype.fetch).toHaveBeenCalledWith(cwd, "pull/2368/head:pr/2368", "upstream"); + + expect(GitCLIService.prototype.push).toHaveBeenCalledTimes(3); + expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "bp-v1-28f63db", undefined); + expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "bp-v2-28f63db", undefined); + expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "bp-v3-28f63db", undefined); + + expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledTimes(3); + expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith(expect.objectContaining({ + owner: "target-org", + repo: "reponame", + cloneUrl: "https://github.com/target-org/reponame.git", + base: "v1", + })); + expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith(expect.objectContaining({ + owner: "target-org", + repo: "reponame", + cloneUrl: "https://github.com/target-org/reponame.git", + base: "v2", + })); + expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith(expect.objectContaining({ + owner: "target-org", + repo: "reponame", + cloneUrl: "https://github.com/target-org/reponame.git", + base: "v3", + })); + }); + test("with multiple target branches and multiple bp names", async () => { addProcessArgs([ "-tb", @@ -1079,6 +1189,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom1", base: "v1", title: "[v1] PR Title", @@ -1091,6 +1202,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom2", base: "v2", title: "[v2] PR Title", @@ -1103,6 +1215,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom3", base: "v3", title: "[v3] PR Title", @@ -1165,6 +1278,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-failure-head-v1", base: "v1", title: "[v1] PR Title", @@ -1177,6 +1291,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-failure-head-v2", base: "v2", title: "[v2] PR Title", @@ -1189,6 +1304,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-failure-head-v3", base: "v3", title: "[v3] PR Title", @@ -1329,6 +1445,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-failure-head-v1", base: "v1", title: "[v1] PR Title", @@ -1341,6 +1458,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-failure-head-v2", base: "v2", title: "[v2] PR Title", @@ -1353,6 +1471,7 @@ describe("cli runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-failure-head-v3", base: "v3", title: "[v3] PR Title", diff --git a/test/service/runner/cli-gitlab-runner.test.ts b/test/service/runner/cli-gitlab-runner.test.ts index 0345910..d75f534 100644 --- a/test/service/runner/cli-gitlab-runner.test.ts +++ b/test/service/runner/cli-gitlab-runner.test.ts @@ -180,6 +180,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-9e15674", base: "target", title: "[target] Update test.txt opened", @@ -239,6 +240,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-ebb1eca", base: "target", title: "[target] Update test.txt", @@ -299,6 +301,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -356,6 +359,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -406,6 +410,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-ebb1eca", base: "target", title: "[target] Update test.txt", @@ -455,6 +460,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-ebb1eca", base: "target", title: "[target] Update test.txt", @@ -500,6 +506,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -544,6 +551,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-e4dd336", base: "target", title: "[target] Update test.txt", @@ -592,6 +600,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-e4dd336-974519f", base: "target", title: "[target] Update test.txt opened", @@ -636,6 +645,7 @@ describe("cli runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-e4dd336", base: "target", title: "[target] Update test.txt", diff --git a/test/service/runner/gha-github-runner.test.ts b/test/service/runner/gha-github-runner.test.ts index 37c5220..ac72388 100644 --- a/test/service/runner/gha-github-runner.test.ts +++ b/test/service/runner/gha-github-runner.test.ts @@ -123,6 +123,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -177,6 +178,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-9174896", base: "target", title: "[target] PR Title", @@ -228,6 +230,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -280,6 +283,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -327,6 +331,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -374,6 +379,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -418,6 +424,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -464,6 +471,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -510,6 +518,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-0404fb9-11da4e3", base: "target", title: "[target] PR Title", @@ -557,6 +566,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -603,6 +613,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -649,6 +660,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-target-28f63db", base: "target", title: "[target] PR Title", @@ -703,6 +715,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v1-28f63db", base: "v1", title: "[v1] PR Title", @@ -715,6 +728,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v2-28f63db", base: "v2", title: "[v2] PR Title", @@ -727,6 +741,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "bp-v3-28f63db", base: "v3", title: "[v3] PR Title", @@ -781,6 +796,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-v1", base: "v1", title: "[v1] PR Title", @@ -793,6 +809,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-v2", base: "v2", title: "[v2] PR Title", @@ -805,6 +822,7 @@ describe("gha runner", () => { expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "owner", repo: "reponame", + cloneUrl: "https://github.com/owner/reponame.git", head: "custom-v3", base: "v3", title: "[v3] PR Title", diff --git a/test/service/runner/gha-gitlab-runner.test.ts b/test/service/runner/gha-gitlab-runner.test.ts index d2d7823..f940ffa 100644 --- a/test/service/runner/gha-gitlab-runner.test.ts +++ b/test/service/runner/gha-gitlab-runner.test.ts @@ -134,6 +134,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-9e15674", base: "target", title: "[target] Update test.txt opened", @@ -186,6 +187,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-ebb1eca", base: "target", title: "[target] Update test.txt", @@ -236,6 +238,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -287,6 +290,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp_branch_name", base: "target", title: "New Title", @@ -332,6 +336,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-ebb1eca", base: "target", title: "[target] Update test.txt", @@ -376,6 +381,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-ebb1eca", base: "target", title: "[target] Update test.txt", @@ -420,6 +426,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-prod-ebb1eca", base: "prod", title: "New Title", @@ -462,6 +469,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-e4dd336", base: "target", title: "[target] Update test.txt", @@ -508,6 +516,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-e4dd336-974519f", base: "target", title: "[target] Update test.txt opened", @@ -550,6 +559,7 @@ describe("gha runner", () => { expect(GitLabClient.prototype.createPullRequest).toHaveBeenCalledWith({ owner: "superuser", repo: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git", head: "bp-target-e4dd336", base: "target", title: "[target] Update test.txt",