From 3e74f934846cb6a53b50b7d81f13386a60770177 Mon Sep 17 00:00:00 2001 From: Joshua Schmid Date: Tue, 28 Nov 2023 17:31:35 +0100 Subject: [PATCH] alternative solution Signed-off-by: Joshua Schmid --- src/backport.ts | 59 +++++++++++++++++++++------ src/github.ts | 104 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 121 insertions(+), 42 deletions(-) diff --git a/src/backport.ts b/src/backport.ts index d8f9cd9..590c9d9 100644 --- a/src/backport.ts +++ b/src/backport.ts @@ -1,7 +1,11 @@ import * as core from "@actions/core"; import dedent from "dedent"; -import { CreatePullRequestResponse, PullRequest } from "./github"; +import { + CreatePullRequestResponse, + PullRequest, + MergeStrategy, +} from "./github"; import { GithubApi } from "./github"; import { Git, GitRefNotFoundError } from "./git"; import * as utils from "./utils"; @@ -86,19 +90,48 @@ export class Backport { let commitShasToCherryPick: string[]; - // find out if "squashed and merged" or "rebased and merged" - if (await this.github.isSquashed(mainpr) || mainpr.commits == 1) { - console.log("PR was squashed and merged"); - // if squashed, then use the merge_commit_sha - commitShasToCherryPick = [ - await this.github.getMergeCommitSha(mainpr), - ]?.filter(Boolean) as string[]; - } else { - // if rebased, then use all the commits from the original PR - console.log("PR was rebased and merged"); - commitShasToCherryPick = commitShas; + /* + + squashed: + - > single parent + - > the parent of the `merge_commit_sha` is NOT associated with the PR + + => use merge_commit_sha + + rebased single-commit: + - > single parent + - > the parent of the `merge_commit_sha` is associated with the PR + + => use commits associated with the PR + + rebased multi-commit: + - > single parent, + - > the parent of the `merge_commit_sha` is associated with the PR + + => use commits associated with the PR + + merge-commit: + - > multiple parents + + => use commits associated with the PR + */ + // switch case to check if it is a squash, rebase, or merge commit + switch (await this.github.mergeStrategy(mainpr)) { + case MergeStrategy.SQUASHED: + commitShasToCherryPick = [ + await this.github.getMergeCommitSha(mainpr), + ]?.filter(Boolean) as string[]; + case MergeStrategy.REBASED: + commitShasToCherryPick = commitShas; + case MergeStrategy.MERGECOMMIT: + commitShasToCherryPick = commitShas; + case MergeStrategy.UNKNOWN: + // probably write a comment + console.log("Could not detect merge strategy."); + return; + default: + commitShasToCherryPick = []; } - console.log(`Found commits: ${commitShas}`); console.log("Checking the merged pull request for merge commits"); diff --git a/src/github.ts b/src/github.ts index fb20006..6be1a2b 100644 --- a/src/github.ts +++ b/src/github.ts @@ -22,7 +22,7 @@ export interface GithubApi { requestReviewers(request: ReviewRequest): Promise; setAssignees(pr: number, assignees: string[]): Promise; setMilestone(pr: number, milestone: number): Promise; - isSquashed(pull: PullRequest): Promise; + mergeStrategy(pull: PullRequest): Promise; getMergeCommitSha(pull: PullRequest): Promise; } @@ -172,18 +172,13 @@ export class Github implements GithubApi { } /** - * Retrieves the parent commit SHA of a given commit. - * If the commit is a merge commit, it returns the SHA of the first parent commit. + * Retrieves the parents of a commit. * @param sha - The SHA of the commit. - * @returns The SHA of the parent commit. + * @returns A promise that resolves to the parents of the commit. */ - public async getParent(sha: string) { + public async getParents(sha: string) { const commit = await this.getCommit(sha); - // a commit has a parent. If it has more than one parent it is an indication - // that it is a merge commit. The first parent is the commit that was merged - // we can safely ignore the second parent as we're checking if the commit isn't a - // merge commit before. - return commit.data.parents[0].sha; + return commit.data.parents; } /** @@ -213,33 +208,84 @@ export class Github implements GithubApi { return assoc_pr_data.some((pr) => pr.number == pull.number); } - /** - * Checks if a pull request is "squashed and merged" - * or "rebased and merged" - * @param pull - The pull request to check. - * @returns A promise that resolves to a boolean indicating whether the pull request is squashed and merged. - */ - public async isSquashed(pull: PullRequest): Promise { + public async getMergeCommitShaAndParents(pull: PullRequest) { const merge_commit_sha = await this.getMergeCommitSha(pull); if (!merge_commit_sha) { console.log("likely not merged yet."); - return false; + return null; + } + const parents = await this.getParents(merge_commit_sha); + return { merge_commit_sha, parents }; + } + + public async isMergeCommit(parents: any[]): Promise { + return parents.length > 1; + } + + public async isRebased( + first_parent_sha: string, + merge_commit_sha: string, + pull: PullRequest, + ): Promise { + const parent_belongs_to_pr = await this.isShaAssociatedWithPullRequest( + first_parent_sha, + pull, + ); + const merge_belongs_to_pr = await this.isShaAssociatedWithPullRequest( + merge_commit_sha, + pull, + ); + return parent_belongs_to_pr && merge_belongs_to_pr; + } + + public async isSquashed( + parent_belongs_to_pr: boolean, + merge_belongs_to_pr: boolean, + ): Promise { + return !parent_belongs_to_pr && merge_belongs_to_pr; + } + + public async mergeStrategy(pull: PullRequest) { + const result = await this.getMergeCommitShaAndParents(pull); + if (!result) return null; + + const { merge_commit_sha, parents } = result; + + if (await this.isMergeCommit(parents)) { + console.log("PR was merged using a merge commit"); + return MergeStrategy.MERGECOMMIT; + } + + const first_parent_sha = parents[0].sha; + if (await this.isRebased(first_parent_sha, merge_commit_sha, pull)) { + console.log("PR was merged using a rebase"); + return MergeStrategy.REBASED; } - // To detect if this was a rebase and merge, we can verify - // that the parent of the merge commit is associated with the pull request - // if it is, we have a "rebase and merge". - // if it is not, we have a "squash and merge". - const parent_commit = await this.getParent(merge_commit_sha); - const is_associated = - (await this.isShaAssociatedWithPullRequest(parent_commit, pull)) && - (await this.isShaAssociatedWithPullRequest(merge_commit_sha, pull)); - if (is_associated) { - return false; + + const parent_belongs_to_pr = await this.isShaAssociatedWithPullRequest( + first_parent_sha, + pull, + ); + const merge_belongs_to_pr = await this.isShaAssociatedWithPullRequest( + merge_commit_sha, + pull, + ); + if (await this.isSquashed(parent_belongs_to_pr, merge_belongs_to_pr)) { + console.log("PR was merged using a squash"); + return MergeStrategy.SQUASHED; } - return true; + + return MergeStrategy.UNKNOWN; } } +export enum MergeStrategy { + SQUASHED = "squashed", + REBASED = "rebased", + MERGECOMMIT = "mergecommit", + UNKNOWN = "unknown", +} + export type PullRequest = { number: number; title: string;