feat(#181): reconstruct script in failure comment (#182)

* feat(#181): reconstruct script in failure comment

* test: ensure steps after failure are still present
This commit is contained in:
oliverpool
2026-01-25 18:39:11 +01:00
committed by GitHub
parent baae3fe1e3
commit 430523aefa
5 changed files with 528 additions and 161 deletions

174
dist/cli/index.js vendored
View File

@@ -1599,6 +1599,7 @@ class Runner {
// we need sequential backporting as they will operate on the same folder
// avoid cloning the same repo multiple times
for (const pr of backportPRs) {
this.logger.setContext(pr.base);
try {
await this.executeBackport(configs, pr, {
gitClientType: gitClientType,
@@ -1607,15 +1608,9 @@ class Runner {
});
}
catch (error) {
this.logger.error(`Something went wrong backporting to ${pr.base}: ${error}`);
if (!configs.dryRun && configs.errorNotification.enabled && configs.errorNotification.message.length > 0) {
// notify the failure as comment in the original pull request
let comment = (0, runner_util_1.injectError)(configs.errorNotification.message, error);
comment = (0, runner_util_1.injectTargetBranch)(comment, pr.base);
await gitApi.createPullRequestComment(configs.originalPullRequest.url, comment);
}
failures.push(error);
}
this.logger.clearContext();
}
if (failures.length > 0) {
throw new Error(`Failure occurred during one of the backports: [${failures.join(" ; ")}]`);
@@ -1641,41 +1636,144 @@ class Runner {
return token;
}
async executeBackport(configs, backportPR, git) {
this.logger.setContext(backportPR.base);
const originalPR = configs.originalPullRequest;
// 4. clone the repository
this.logger.debug("Cloning repo..");
await git.gitCli.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, backportPR.base);
// 5. create new branch from target one and checkout
this.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 ||
configs.originalPullRequest.state === "open") {
this.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}`);
let i = 0;
for (const step of backportSteps(this.logger, configs, backportPR, git)) {
try {
await step();
}
catch (error) {
this.logger.error(`Something went wrong backporting to ${backportPR.base}: ${error}`);
if (!configs.dryRun && configs.errorNotification.enabled && configs.errorNotification.message.length > 0) {
// notify the failure as comment in the original pull request
let comment = (0, runner_util_1.injectError)(configs.errorNotification.message, error);
comment = (0, runner_util_1.injectTargetBranch)(comment, backportPR.base);
try {
let script = "\n\nReconstruction of the attempted steps (beware that escaping may be missing):\n```sh\n";
script += await backportScript(configs, backportPR, git, i);
script += "```";
comment += script;
}
catch (scriptError) {
this.logger.error(`Something went wrong reconstructing the script: ${scriptError}`);
}
await git.gitClientApi.createPullRequestComment(configs.originalPullRequest.url, comment);
}
throw error;
}
i++;
}
// 7. apply all changes to the new branch
this.logger.debug("Cherry picking commits..");
for (const sha of originalPR.commits) {
await git.gitCli.cherryPick(configs.folder, sha, configs.mergeStrategy, configs.mergeStrategyOption, configs.cherryPickOptions);
}
if (!configs.dryRun) {
// 8. push the new branch to origin
await git.gitCli.push(configs.folder, backportPR.head);
// 9. create pull request new branch -> target branch (using octokit)
const prUrl = await git.gitClientApi.createPullRequest(backportPR);
this.logger.info(`Pull request created: ${prUrl}`);
}
else {
this.logger.warn("Pull request creation and remote push skipped");
this.logger.info(`${JSON.stringify(backportPR, null, 2)}`);
}
this.logger.clearContext();
}
}
exports["default"] = Runner;
function* backportSteps(logger, configs, backportPR, git) {
// every failible operation should be in one dedicated closure
// 4. clone the repository
yield async () => {
logger.debug("Cloning repo..");
await git.gitCli.clone(configs.originalPullRequest.targetRepo.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 ||
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}`);
};
}
// 7. apply all changes to the new branch
yield async () => {
logger.debug("Cherry picking commits..");
};
for (const sha of configs.originalPullRequest.commits) {
yield async () => {
await git.gitCli.cherryPick(configs.folder, sha, configs.mergeStrategy, configs.mergeStrategyOption, configs.cherryPickOptions);
};
}
if (!configs.dryRun) {
// 8. push the new branch to origin
yield async () => {
await git.gitCli.push(configs.folder, backportPR.head);
};
// 9. create pull request new branch -> target branch (using octokit)
yield async () => {
const prUrl = await git.gitClientApi.createPullRequest(backportPR);
logger.info(`Pull request created: ${prUrl}`);
};
}
else {
yield async () => {
logger.warn("Pull request creation and remote push skipped");
logger.info(`${JSON.stringify(backportPR, null, 2)}`);
};
}
}
// backportScript reconstruct the git commands that were run to attempt the backport.
async function backportScript(configs, backportPR, git, failed) {
let s = "";
const fakeLogger = {
debug: function (_message) { },
info: function (_message) { },
warn: function (_message) { },
};
const fakeGitCli = {
async clone(_from, _to, _branch) {
/* consider that the user already has the repo cloned (or knows how to clone) */
s += `git fetch origin ${backportPR.base}`;
},
async createLocalBranch(_cwd, newBranch) {
s += `git switch -c ${newBranch} origin/${backportPR.base}`;
},
async fetch(_cwd, branch, remote = "origin") {
s += `git fetch ${remote} ${branch}`;
},
async cherryPick(_cwd, sha, strategy = "recursive", strategyOption = "theirs", cherryPickOptions) {
s += `git cherry-pick -m 1 --strategy=${strategy} --strategy-option=${strategyOption} `;
if (cherryPickOptions !== undefined) {
s += cherryPickOptions + " ";
}
s += sha;
},
async push(_cwd, branch, remote = "origin", force = false) {
s += `git push ${remote} ${branch}`;
if (force) {
s += " --force";
}
}
};
let i = 0;
let steps = "";
for (const step of backportSteps(fakeLogger, configs, backportPR, {
gitClientType: git.gitClientType,
gitClientApi: {
async createPullRequest(_backport) {
s += `# ${git.gitClientType}.createPullRequest`;
return "";
},
async createPullRequestComment(_prUrl, _comment) {
s += `# ${git.gitClientType}.createPullRequestComment`;
return "";
}
},
gitCli: fakeGitCli,
})) {
if (i == failed) {
s += "# the step below failed\n";
}
await step();
if (s.length > 0 || i == failed) {
steps += s + "\n";
s = "";
}
i++;
}
return steps;
}
/***/ }),

174
dist/gha/index.js vendored
View File

@@ -1564,6 +1564,7 @@ class Runner {
// we need sequential backporting as they will operate on the same folder
// avoid cloning the same repo multiple times
for (const pr of backportPRs) {
this.logger.setContext(pr.base);
try {
await this.executeBackport(configs, pr, {
gitClientType: gitClientType,
@@ -1572,15 +1573,9 @@ class Runner {
});
}
catch (error) {
this.logger.error(`Something went wrong backporting to ${pr.base}: ${error}`);
if (!configs.dryRun && configs.errorNotification.enabled && configs.errorNotification.message.length > 0) {
// notify the failure as comment in the original pull request
let comment = (0, runner_util_1.injectError)(configs.errorNotification.message, error);
comment = (0, runner_util_1.injectTargetBranch)(comment, pr.base);
await gitApi.createPullRequestComment(configs.originalPullRequest.url, comment);
}
failures.push(error);
}
this.logger.clearContext();
}
if (failures.length > 0) {
throw new Error(`Failure occurred during one of the backports: [${failures.join(" ; ")}]`);
@@ -1606,41 +1601,144 @@ class Runner {
return token;
}
async executeBackport(configs, backportPR, git) {
this.logger.setContext(backportPR.base);
const originalPR = configs.originalPullRequest;
// 4. clone the repository
this.logger.debug("Cloning repo..");
await git.gitCli.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, backportPR.base);
// 5. create new branch from target one and checkout
this.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 ||
configs.originalPullRequest.state === "open") {
this.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}`);
let i = 0;
for (const step of backportSteps(this.logger, configs, backportPR, git)) {
try {
await step();
}
catch (error) {
this.logger.error(`Something went wrong backporting to ${backportPR.base}: ${error}`);
if (!configs.dryRun && configs.errorNotification.enabled && configs.errorNotification.message.length > 0) {
// notify the failure as comment in the original pull request
let comment = (0, runner_util_1.injectError)(configs.errorNotification.message, error);
comment = (0, runner_util_1.injectTargetBranch)(comment, backportPR.base);
try {
let script = "\n\nReconstruction of the attempted steps (beware that escaping may be missing):\n```sh\n";
script += await backportScript(configs, backportPR, git, i);
script += "```";
comment += script;
}
catch (scriptError) {
this.logger.error(`Something went wrong reconstructing the script: ${scriptError}`);
}
await git.gitClientApi.createPullRequestComment(configs.originalPullRequest.url, comment);
}
throw error;
}
i++;
}
// 7. apply all changes to the new branch
this.logger.debug("Cherry picking commits..");
for (const sha of originalPR.commits) {
await git.gitCli.cherryPick(configs.folder, sha, configs.mergeStrategy, configs.mergeStrategyOption, configs.cherryPickOptions);
}
if (!configs.dryRun) {
// 8. push the new branch to origin
await git.gitCli.push(configs.folder, backportPR.head);
// 9. create pull request new branch -> target branch (using octokit)
const prUrl = await git.gitClientApi.createPullRequest(backportPR);
this.logger.info(`Pull request created: ${prUrl}`);
}
else {
this.logger.warn("Pull request creation and remote push skipped");
this.logger.info(`${JSON.stringify(backportPR, null, 2)}`);
}
this.logger.clearContext();
}
}
exports["default"] = Runner;
function* backportSteps(logger, configs, backportPR, git) {
// every failible operation should be in one dedicated closure
// 4. clone the repository
yield async () => {
logger.debug("Cloning repo..");
await git.gitCli.clone(configs.originalPullRequest.targetRepo.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 ||
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}`);
};
}
// 7. apply all changes to the new branch
yield async () => {
logger.debug("Cherry picking commits..");
};
for (const sha of configs.originalPullRequest.commits) {
yield async () => {
await git.gitCli.cherryPick(configs.folder, sha, configs.mergeStrategy, configs.mergeStrategyOption, configs.cherryPickOptions);
};
}
if (!configs.dryRun) {
// 8. push the new branch to origin
yield async () => {
await git.gitCli.push(configs.folder, backportPR.head);
};
// 9. create pull request new branch -> target branch (using octokit)
yield async () => {
const prUrl = await git.gitClientApi.createPullRequest(backportPR);
logger.info(`Pull request created: ${prUrl}`);
};
}
else {
yield async () => {
logger.warn("Pull request creation and remote push skipped");
logger.info(`${JSON.stringify(backportPR, null, 2)}`);
};
}
}
// backportScript reconstruct the git commands that were run to attempt the backport.
async function backportScript(configs, backportPR, git, failed) {
let s = "";
const fakeLogger = {
debug: function (_message) { },
info: function (_message) { },
warn: function (_message) { },
};
const fakeGitCli = {
async clone(_from, _to, _branch) {
/* consider that the user already has the repo cloned (or knows how to clone) */
s += `git fetch origin ${backportPR.base}`;
},
async createLocalBranch(_cwd, newBranch) {
s += `git switch -c ${newBranch} origin/${backportPR.base}`;
},
async fetch(_cwd, branch, remote = "origin") {
s += `git fetch ${remote} ${branch}`;
},
async cherryPick(_cwd, sha, strategy = "recursive", strategyOption = "theirs", cherryPickOptions) {
s += `git cherry-pick -m 1 --strategy=${strategy} --strategy-option=${strategyOption} `;
if (cherryPickOptions !== undefined) {
s += cherryPickOptions + " ";
}
s += sha;
},
async push(_cwd, branch, remote = "origin", force = false) {
s += `git push ${remote} ${branch}`;
if (force) {
s += " --force";
}
}
};
let i = 0;
let steps = "";
for (const step of backportSteps(fakeLogger, configs, backportPR, {
gitClientType: git.gitClientType,
gitClientApi: {
async createPullRequest(_backport) {
s += `# ${git.gitClientType}.createPullRequest`;
return "";
},
async createPullRequestComment(_prUrl, _comment) {
s += `# ${git.gitClientType}.createPullRequestComment`;
return "";
}
},
gitCli: fakeGitCli,
})) {
if (i == failed) {
s += "# the step below failed\n";
}
await step();
if (s.length > 0 || i == failed) {
steps += s + "\n";
s = "";
}
i++;
}
return steps;
}
/***/ }),

View File

@@ -5,7 +5,7 @@ import PullRequestConfigsParser from "@bp/service/configs/pullrequest/pr-configs
import GitCLIService from "@bp/service/git/git-cli";
import GitClient from "@bp/service/git/git-client";
import GitClientFactory from "@bp/service/git/git-client-factory";
import { BackportPullRequest, GitClientType, GitPullRequest } from "@bp/service/git/git.types";
import { BackportPullRequest, GitClientType } from "@bp/service/git/git.types";
import LoggerService from "@bp/service/logger/logger-service";
import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
import { inferGitClient, inferGitApiUrl, getGitTokenFromEnv } from "@bp/service/git/git-util";
@@ -13,8 +13,8 @@ import { injectError, injectTargetBranch } from "./runner-util";
interface Git {
gitClientType: GitClientType;
gitClientApi: GitClient;
gitCli: GitCLIService;
gitClientApi: Pick<GitClient, ("createPullRequest" | "createPullRequestComment")>;
gitCli: Pick<GitCLIService, ("clone" | "createLocalBranch" | "fetch" | "cherryPick" | "push")>;
}
/**
@@ -84,23 +84,18 @@ export default class Runner {
const failures: string[] = [];
// we need sequential backporting as they will operate on the same folder
// avoid cloning the same repo multiple times
for(const pr of backportPRs) {
for (const pr of backportPRs) {
this.logger.setContext(pr.base);
try {
await this.executeBackport(configs, pr, {
gitClientType: gitClientType,
gitClientApi: gitApi,
gitCli: git,
});
} catch(error) {
this.logger.error(`Something went wrong backporting to ${pr.base}: ${error}`);
if (!configs.dryRun && configs.errorNotification.enabled && configs.errorNotification.message.length > 0) {
// notify the failure as comment in the original pull request
let comment = injectError(configs.errorNotification.message, error as string);
comment = injectTargetBranch(comment, pr.base);
await gitApi.createPullRequestComment(configs.originalPullRequest.url, comment);
}
} catch (error) {
failures.push(error as string);
}
this.logger.clearContext();
}
if (failures.length > 0) {
@@ -130,45 +125,147 @@ export default class Runner {
}
async executeBackport(configs: Configs, backportPR: BackportPullRequest, git: Git): Promise<void> {
this.logger.setContext(backportPR.base);
const originalPR: GitPullRequest = configs.originalPullRequest;
// 4. clone the repository
this.logger.debug("Cloning repo..");
await git.gitCli.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, backportPR.base);
// 5. create new branch from target one and checkout
this.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 ||
configs.originalPullRequest.state === "open") {
this.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}`);
let i = 0;
for (const step of backportSteps(this.logger, configs, backportPR, git)) {
try {
await step();
} catch (error) {
this.logger.error(`Something went wrong backporting to ${backportPR.base}: ${error}`);
if (!configs.dryRun && configs.errorNotification.enabled && configs.errorNotification.message.length > 0) {
// notify the failure as comment in the original pull request
let comment = injectError(configs.errorNotification.message, error as string);
comment = injectTargetBranch(comment, backportPR.base);
try {
let script = "\n\nReconstruction of the attempted steps (beware that escaping may be missing):\n```sh\n";
script += await backportScript(configs, backportPR, git, i);
script += "```";
comment += script;
} catch (scriptError) {
this.logger.error(`Something went wrong reconstructing the script: ${scriptError}`);
}
await git.gitClientApi.createPullRequestComment(configs.originalPullRequest.url, comment);
}
throw error;
}
i++;
}
// 7. apply all changes to the new branch
this.logger.debug("Cherry picking commits..");
for (const sha of originalPR.commits) {
await git.gitCli.cherryPick(configs.folder, sha, configs.mergeStrategy, configs.mergeStrategyOption, configs.cherryPickOptions);
}
if (!configs.dryRun) {
// 8. push the new branch to origin
await git.gitCli.push(configs.folder, backportPR.head);
// 9. create pull request new branch -> target branch (using octokit)
const prUrl = await git.gitClientApi.createPullRequest(backportPR);
this.logger.info(`Pull request created: ${prUrl}`);
} else {
this.logger.warn("Pull request creation and remote push skipped");
this.logger.info(`${JSON.stringify(backportPR, null, 2)}`);
}
this.logger.clearContext();
}
}
function* backportSteps(logger: Pick<LoggerService, "debug" | "info" | "warn">, configs: Configs, backportPR: BackportPullRequest, git: Git): Generator<() => Promise<void>, void, unknown> {
// every failible operation should be in one dedicated closure
// 4. clone the repository
yield async () => {
logger.debug("Cloning repo..");
await git.gitCli.clone(configs.originalPullRequest.targetRepo.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 ||
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}`);
};
}
// 7. apply all changes to the new branch
yield async () => {
logger.debug("Cherry picking commits..");
};
for (const sha of configs.originalPullRequest.commits) {
yield async () => {
await git.gitCli.cherryPick(configs.folder, sha, configs.mergeStrategy, configs.mergeStrategyOption, configs.cherryPickOptions);
};
}
if (!configs.dryRun) {
// 8. push the new branch to origin
yield async () => {
await git.gitCli.push(configs.folder, backportPR.head);
};
// 9. create pull request new branch -> target branch (using octokit)
yield async () => {
const prUrl = await git.gitClientApi.createPullRequest(backportPR);
logger.info(`Pull request created: ${prUrl}`);
};
} else {
yield async () => {
logger.warn("Pull request creation and remote push skipped");
logger.info(`${JSON.stringify(backportPR, null, 2)}`);
};
}
}
// backportScript reconstruct the git commands that were run to attempt the backport.
async function backportScript(configs: Configs, backportPR: BackportPullRequest, git: Git, failed: number): Promise<string> {
let s = "";
const fakeLogger = {
debug: function(_message: string): void { /*discard*/ },
info: function(_message: string): void { /*discard*/ },
warn: function(_message: string): void { /*discard*/ },
};
const fakeGitCli: Git["gitCli"] = {
async clone(_from: string, _to: string, _branch: string): Promise<void> {
/* consider that the user already has the repo cloned (or knows how to clone) */
s += `git fetch origin ${backportPR.base}`;
},
async createLocalBranch(_cwd: string, newBranch: string): Promise<void> {
s += `git switch -c ${newBranch} origin/${backportPR.base}`;
},
async fetch(_cwd: string, branch: string, remote = "origin"): Promise<void> {
s += `git fetch ${remote} ${branch}`;
},
async cherryPick(_cwd: string, sha: string, strategy = "recursive", strategyOption = "theirs", cherryPickOptions: string | undefined): Promise<void> {
s += `git cherry-pick -m 1 --strategy=${strategy} --strategy-option=${strategyOption} `;
if (cherryPickOptions !== undefined) {
s += cherryPickOptions + " ";
}
s += sha;
},
async push(_cwd: string, branch: string, remote = "origin", force = false): Promise<void> {
s += `git push ${remote} ${branch}`;
if (force) {
s += " --force";
}
}
};
let i = 0;
let steps = "";
for (const step of backportSteps(fakeLogger, configs, backportPR, {
gitClientType: git.gitClientType,
gitClientApi: {
async createPullRequest(_backport: BackportPullRequest): Promise<string> {
s += `# ${git.gitClientType}.createPullRequest`;
return "";
},
async createPullRequestComment(_prUrl: string, _comment: string): Promise<string | undefined> {
s += `# ${git.gitClientType}.createPullRequestComment`;
return "";
}
},
gitCli: fakeGitCli,
})) {
if (i == failed) {
s += "# the step below failed\n";
}
await step();
if (s.length > 0 || i == failed) {
steps += s + "\n";
s = "";
}
i++;
}
return steps;
}

View File

@@ -1241,6 +1241,13 @@ describe("cli runner", () => {
const createPullRequestSpy = jest.spyOn(GitHubClient.prototype, "createPullRequest").mockImplementation((backport: BackportPullRequest) => {
throw new Error(`Mocked error: ${backport.base}`);
});
let cherryPicked=0;
const cherryPickSpy = jest.spyOn(GitCLIService.prototype, "cherryPick").mockImplementation(async (_cwd: string, sha: string, _strategy: string | undefined, _strategyOption: string | undefined, _cherryPickOptions: string | undefined) => {
cherryPicked++;
if(cherryPicked==3){
throw new Error(`Cherry-pick error 3: ${sha}`);
}
});
addProcessArgs([
"-tb",
@@ -1254,7 +1261,7 @@ describe("cli runner", () => {
"--enable-err-notification",
]);
await expect(() => runner.execute()).rejects.toThrow("Failure occurred during one of the backports: [Error: Mocked error: v1 ; Error: Mocked error: v2 ; Error: Mocked error: v3]");
await expect(() => runner.execute()).rejects.toThrow("Failure occurred during one of the backports: [Error: Mocked error: v1 ; Error: Mocked error: v2 ; Error: Cherry-pick error 3: 28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc]");
const cwd = "/tmp/folder";
@@ -1279,12 +1286,12 @@ describe("cli runner", () => {
expect(GitCLIService.prototype.cherryPick).toHaveBeenCalledWith(cwd, "28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc", undefined, undefined, undefined);
expect(GitCLIService.prototype.cherryPick).toHaveBeenCalledWith(cwd, "28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc", undefined, undefined, undefined);
expect(GitCLIService.prototype.push).toHaveBeenCalledTimes(3);
expect(GitCLIService.prototype.push).toHaveBeenCalledTimes(2);
expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "custom-failure-head-v1");
expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "custom-failure-head-v2");
expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "custom-failure-head-v3");
//expect(GitCLIService.prototype.push).toHaveBeenCalledWith(cwd, "custom-failure-head-v3"); cherry-pick failed
expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledTimes(3);
expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledTimes(2);
expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({
owner: "owner",
repo: "reponame",
@@ -1309,25 +1316,59 @@ describe("cli runner", () => {
labels: [],
comments: [],
});
expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({
owner: "owner",
repo: "reponame",
head: "custom-failure-head-v3",
base: "v3",
title: "[v3] PR Title",
body: "**Backport:** https://codeberg.org/owner/reponame/pulls/2368\r\n\r\nPlease review and merge",
reviewers: ["gh-user", "that-s-a-user"],
assignees: [],
labels: [],
comments: [],
});
// expect(GitHubClient.prototype.createPullRequest).toHaveBeenCalledWith({
// owner: "owner",
// repo: "reponame",
// head: "custom-failure-head-v3",
// base: "v3",
// title: "[v3] PR Title",
// body: "**Backport:** https://codeberg.org/owner/reponame/pulls/2368\r\n\r\nPlease review and merge",
// reviewers: ["gh-user", "that-s-a-user"],
// assignees: [],
// labels: [],
// comments: [],
// });
expect(GitHubClient.prototype.createPullRequest).toThrow();
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledTimes(3);
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://codeberg.org/api/v1/repos/owner/reponame/pulls/2368", "The backport to `v1` failed. Check the latest run for more details.");
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://codeberg.org/api/v1/repos/owner/reponame/pulls/2368", "The backport to `v2` failed. Check the latest run for more details.");
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://codeberg.org/api/v1/repos/owner/reponame/pulls/2368", "The backport to `v3` failed. Check the latest run for more details.");
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://codeberg.org/api/v1/repos/owner/reponame/pulls/2368", `The backport to ${"`v1`"} failed. Check the latest run for more details.
Reconstruction of the attempted steps (beware that escaping may be missing):
${"```sh"}
git fetch origin v1
git switch -c custom-failure-head-v1 origin/v1
git fetch origin pull/2368/head:pr/2368
git cherry-pick -m 1 --strategy=recursive --strategy-option=theirs 28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc
git push origin custom-failure-head-v1
# the step below failed
# codeberg.createPullRequest
${"```"}`);
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://codeberg.org/api/v1/repos/owner/reponame/pulls/2368", `The backport to ${"`v2`"} failed. Check the latest run for more details.
Reconstruction of the attempted steps (beware that escaping may be missing):
${"```sh"}
git fetch origin v2
git switch -c custom-failure-head-v2 origin/v2
git fetch origin pull/2368/head:pr/2368
git cherry-pick -m 1 --strategy=recursive --strategy-option=theirs 28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc
git push origin custom-failure-head-v2
# the step below failed
# codeberg.createPullRequest
${"```"}`);
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://codeberg.org/api/v1/repos/owner/reponame/pulls/2368", `The backport to ${"`v3`"} failed. Check the latest run for more details.
Reconstruction of the attempted steps (beware that escaping may be missing):
${"```sh"}
git fetch origin v3
git switch -c custom-failure-head-v3 origin/v3
git fetch origin pull/2368/head:pr/2368
# the step below failed
git cherry-pick -m 1 --strategy=recursive --strategy-option=theirs 28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc
git push origin custom-failure-head-v3
# codeberg.createPullRequest
${"```"}`);
createPullRequestSpy.mockReset();
cherryPickSpy.mockReset();
});
test("with some failures and dry run enabled", async () => {

View File

@@ -1323,9 +1323,42 @@ describe("cli runner", () => {
});
expect(GitHubClient.prototype.createPullRequest).toThrow();
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledTimes(3);
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://api.github.com/repos/owner/reponame/pulls/2368", "The backport to `v1` failed. Check the latest run for more details.");
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://api.github.com/repos/owner/reponame/pulls/2368", "The backport to `v2` failed. Check the latest run for more details.");
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://api.github.com/repos/owner/reponame/pulls/2368", "The backport to `v3` failed. Check the latest run for more details.");
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://api.github.com/repos/owner/reponame/pulls/2368", `The backport to ${"`v1`"} failed. Check the latest run for more details.
Reconstruction of the attempted steps (beware that escaping may be missing):
${"```sh"}
git fetch origin v1
git switch -c custom-failure-head-v1 origin/v1
git fetch origin pull/2368/head:pr/2368
git cherry-pick -m 1 --strategy=recursive --strategy-option=theirs 28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc
git push origin custom-failure-head-v1
# the step below failed
# github.createPullRequest
${"```"}`);
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://api.github.com/repos/owner/reponame/pulls/2368", `The backport to ${"`v2`"} failed. Check the latest run for more details.
Reconstruction of the attempted steps (beware that escaping may be missing):
${"```sh"}
git fetch origin v2
git switch -c custom-failure-head-v2 origin/v2
git fetch origin pull/2368/head:pr/2368
git cherry-pick -m 1 --strategy=recursive --strategy-option=theirs 28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc
git push origin custom-failure-head-v2
# the step below failed
# github.createPullRequest
${"```"}`);
expect(GitHubClient.prototype.createPullRequestComment).toHaveBeenCalledWith("https://api.github.com/repos/owner/reponame/pulls/2368", `The backport to ${"`v3`"} failed. Check the latest run for more details.
Reconstruction of the attempted steps (beware that escaping may be missing):
${"```sh"}
git fetch origin v3
git switch -c custom-failure-head-v3 origin/v3
git fetch origin pull/2368/head:pr/2368
git cherry-pick -m 1 --strategy=recursive --strategy-option=theirs 28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc
git push origin custom-failure-head-v3
# the step below failed
# github.createPullRequest
${"```"}`);
createPullRequestSpy.mockReset();
});