Skip to content

Commit

Permalink
alternative solution
Browse files Browse the repository at this point in the history
Signed-off-by: Joshua Schmid <[email protected]>
  • Loading branch information
jschmid1 committed Nov 28, 2023
1 parent 3954941 commit 3e74f93
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 42 deletions.
59 changes: 46 additions & 13 deletions src/backport.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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");
Expand Down
104 changes: 75 additions & 29 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface GithubApi {
requestReviewers(request: ReviewRequest): Promise<RequestReviewersResponse>;
setAssignees(pr: number, assignees: string[]): Promise<GenericResponse>;
setMilestone(pr: number, milestone: number): Promise<GenericResponse>;
isSquashed(pull: PullRequest): Promise<boolean>;
mergeStrategy(pull: PullRequest): Promise<string | null>;
getMergeCommitSha(pull: PullRequest): Promise<string | null>;
}

Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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<boolean> {
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<boolean> {
return parents.length > 1;
}

public async isRebased(
first_parent_sha: string,
merge_commit_sha: string,
pull: PullRequest,
): Promise<boolean> {
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<boolean> {
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;
Expand Down

0 comments on commit 3e74f93

Please sign in to comment.