* feat(#181): reconstruct script in failure comment * test: ensure steps after failure are still present
This commit is contained in:
174
dist/cli/index.js
vendored
174
dist/cli/index.js
vendored
@@ -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
174
dist/gha/index.js
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user