mirror of
https://github.com/kiegroup/git-backporting.git
synced 2025-06-29 06:03:50 +00:00
feat: auto-detect the value of the no-squash option (#118)
The auto-no-squash option is added to: * backport all the commits when the pull/merge request has been merged * backport the squashed commit otherwise It is equivalent to dynamically adjust the value of the no-squash option, depending on the context. The no-squash option is kept for backward compatibility for a single use case: backporting the merged commit instead of backporting the commits of the pull/merge request request. Detecting if a pull/merge request was squashed or not depends on the underlying forge: * Forgejo / GitHub: use the API to count the number of parents * GitLab: if the squash_commit_sha is set, the merge request was squashed If the pull/merge request is open, always backport all the commits it contains. Fixes: https://github.com/kiegroup/git-backporting/issues/113 Co-authored-by: Andrea Lamparelli <a.lamparelli95@gmail.com>
This commit is contained in:
parent
fc5dba6703
commit
6042bcc40b
23 changed files with 324 additions and 54 deletions
|
@ -44,6 +44,7 @@ export default abstract class ArgsParser {
|
|||
labels: this.getOrDefault(args.labels, []),
|
||||
inheritLabels: this.getOrDefault(args.inheritLabels, false),
|
||||
squash: this.getOrDefault(args.squash, true),
|
||||
autoNoSquash: this.getOrDefault(args.autoNoSquash, false),
|
||||
strategy: this.getOrDefault(args.strategy),
|
||||
strategyOption: this.getOrDefault(args.strategyOption),
|
||||
cherryPickOptions: this.getOrDefault(args.cherryPickOptions),
|
||||
|
|
|
@ -22,7 +22,8 @@ export interface Args {
|
|||
inheritReviewers?: boolean, // if true and reviewers == [] then inherit reviewers from original pr
|
||||
labels?: string[], // backport pr labels
|
||||
inheritLabels?: boolean, // if true inherit labels from original pr
|
||||
squash?: boolean, // if false use squashed/merged commit otherwise backport all commits as part of the pr
|
||||
squash?: boolean,
|
||||
autoNoSquash?: boolean,
|
||||
strategy?: string, // cherry-pick merge strategy
|
||||
strategyOption?: string, // cherry-pick merge strategy option
|
||||
cherryPickOptions?: string, // additional cherry-pick options
|
||||
|
|
|
@ -28,7 +28,8 @@ export default class CLIArgsParser extends ArgsParser {
|
|||
.option("--no-inherit-reviewers", "if provided and reviewers option is empty then inherit them from original pull request")
|
||||
.option("--labels <labels>", "comma separated list of labels to be assigned to the backported pull request", getAsCommaSeparatedList)
|
||||
.option("--inherit-labels", "if true the backported pull request will inherit labels from the original one")
|
||||
.option("--no-squash", "if provided the tool will backport all commits as part of the pull request")
|
||||
.option("--no-squash", "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")
|
||||
.option("--auto-no-squash", "If the pull request was merged or is open, backport all commits. If the pull request commits were squashed, backport the squashed commit.")
|
||||
.option("--strategy <strategy>", "cherry-pick merge strategy, default to 'recursive'", undefined)
|
||||
.option("--strategy-option <strategy-option>", "cherry-pick merge strategy option, default to 'theirs'")
|
||||
.option("--cherry-pick-options <options>", "additional cherry-pick options")
|
||||
|
@ -66,6 +67,7 @@ export default class CLIArgsParser extends ArgsParser {
|
|||
labels: opts.labels,
|
||||
inheritLabels: opts.inheritLabels,
|
||||
squash: opts.squash,
|
||||
autoNoSquash: opts.autoNoSquash,
|
||||
strategy: opts.strategy,
|
||||
strategyOption: opts.strategyOption,
|
||||
cherryPickOptions: opts.cherryPickOptions,
|
||||
|
|
|
@ -32,6 +32,7 @@ export default class GHAArgsParser extends ArgsParser {
|
|||
labels: getAsCommaSeparatedList(getInput("labels")),
|
||||
inheritLabels: getAsBooleanOrDefault(getInput("inherit-labels")),
|
||||
squash: !getAsBooleanOrDefault(getInput("no-squash")),
|
||||
autoNoSquash: getAsBooleanOrDefault(getInput("auto-no-squash")),
|
||||
strategy: getOrUndefined(getInput("strategy")),
|
||||
strategyOption: getOrUndefined(getInput("strategy-option")),
|
||||
cherryPickOptions: getOrUndefined(getInput("cherry-pick-options")),
|
||||
|
|
|
@ -16,9 +16,12 @@ export default class PullRequestConfigsParser extends ConfigsParser {
|
|||
}
|
||||
|
||||
public async parse(args: Args): Promise<Configs> {
|
||||
let pr: GitPullRequest;
|
||||
let pr: GitPullRequest;
|
||||
if (args.autoNoSquash) {
|
||||
args.squash = undefined;
|
||||
}
|
||||
try {
|
||||
pr = await this.gitClient.getPullRequestFromUrl(args.pullRequest, args.squash!);
|
||||
pr = await this.gitClient.getPullRequestFromUrl(args.pullRequest, args.squash);
|
||||
} catch(error) {
|
||||
this.logger.error("Something went wrong retrieving pull request");
|
||||
throw error;
|
||||
|
|
|
@ -25,7 +25,7 @@ import { BackportPullRequest, GitClientType, GitPullRequest } from "@bp/service/
|
|||
* @param squash if true keep just one single commit, otherwise get the full list
|
||||
* @returns {Promise<PullRequest>}
|
||||
*/
|
||||
getPullRequest(owner: string, repo: string, prNumber: number, squash: boolean): Promise<GitPullRequest>;
|
||||
getPullRequest(owner: string, repo: string, prNumber: number, squash: boolean | undefined): Promise<GitPullRequest>;
|
||||
|
||||
/**
|
||||
* Get a pull request object from the underneath git service
|
||||
|
@ -33,7 +33,7 @@ import { BackportPullRequest, GitClientType, GitPullRequest } from "@bp/service/
|
|||
* @param squash if true keep just one single commit, otherwise get the full list
|
||||
* @returns {Promise<PullRequest>}
|
||||
*/
|
||||
getPullRequestFromUrl(prUrl: string, squash: boolean): Promise<GitPullRequest>;
|
||||
getPullRequestFromUrl(prUrl: string, squash: boolean | undefined): Promise<GitPullRequest>;
|
||||
|
||||
// WRITE
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
|
||||
import { GitClientType } from "@bp/service/git/git.types";
|
||||
import { AuthTokenId } from "@bp/service/configs/configs.types";
|
||||
|
||||
|
@ -41,6 +42,29 @@ export const inferGitApiUrl = (prUrl: string, apiVersion = "v4"): string => {
|
|||
return `${baseUrl}/api/${apiVersion}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Infer the value of the squash option
|
||||
* @param open true if the pull/merge request is still open
|
||||
* @param squash_commit undefined if the pull/merge request was merged, the sha of the squashed commit if it was squashed
|
||||
* @returns true if a single commit must be cherry-picked, false if all merged commits must be cherry-picked
|
||||
*/
|
||||
export const inferSquash = (open: boolean, squash_commit: string | undefined): boolean => {
|
||||
const logger = LoggerServiceFactory.getLogger();
|
||||
|
||||
if (open) {
|
||||
logger.debug("cherry-pick all commits because they have not been merged (or squashed) in the base branch yet");
|
||||
return false;
|
||||
} else {
|
||||
if (squash_commit !== undefined) {
|
||||
logger.debug(`cherry-pick the squashed commit ${squash_commit}`);
|
||||
return true;
|
||||
} else {
|
||||
logger.debug("cherry-pick the merged commit(s)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the git token from env variable, the default is taken from GIT_TOKEN env.
|
||||
* All specific git env variable have precedence and override the default one.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import GitClient from "@bp/service/git/git-client";
|
||||
import { inferSquash } from "@bp/service/git/git-util";
|
||||
import { BackportPullRequest, GitClientType, GitPullRequest } from "@bp/service/git/git.types";
|
||||
import GitHubMapper from "@bp/service/git/github/github-mapper";
|
||||
import OctokitFactory from "@bp/service/git/github/octokit-factory";
|
||||
|
@ -37,7 +38,7 @@ export default class GitHubClient implements GitClient {
|
|||
return "noreply@github.com";
|
||||
}
|
||||
|
||||
async getPullRequest(owner: string, repo: string, prNumber: number, squash = true): Promise<GitPullRequest> {
|
||||
async getPullRequest(owner: string, repo: string, prNumber: number, squash: boolean | undefined): Promise<GitPullRequest> {
|
||||
this.logger.debug(`Fetching pull request ${owner}/${repo}/${prNumber}`);
|
||||
const { data } = await this.octokit.rest.pulls.get({
|
||||
owner: owner,
|
||||
|
@ -45,6 +46,22 @@ export default class GitHubClient implements GitClient {
|
|||
pull_number: prNumber,
|
||||
});
|
||||
|
||||
if (squash === undefined) {
|
||||
let commit_sha: string | undefined = undefined;
|
||||
const open: boolean = data.state == "open";
|
||||
if (!open) {
|
||||
const commit = await this.octokit.rest.git.getCommit({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
commit_sha: (data.merge_commit_sha as string),
|
||||
});
|
||||
if (commit.data.parents.length === 1) {
|
||||
commit_sha = (data.merge_commit_sha as string);
|
||||
}
|
||||
}
|
||||
squash = inferSquash(open, commit_sha);
|
||||
}
|
||||
|
||||
const commits: string[] = [];
|
||||
if (!squash) {
|
||||
// fetch all commits
|
||||
|
@ -64,7 +81,7 @@ export default class GitHubClient implements GitClient {
|
|||
return this.mapper.mapPullRequest(data as PullRequest, commits);
|
||||
}
|
||||
|
||||
async getPullRequestFromUrl(prUrl: string, squash = true): Promise<GitPullRequest> {
|
||||
async getPullRequestFromUrl(prUrl: string, squash: boolean | undefined): Promise<GitPullRequest> {
|
||||
const { owner, project, id } = this.extractPullRequestData(prUrl);
|
||||
return this.getPullRequest(owner, project, id, squash);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import LoggerService from "@bp/service/logger/logger-service";
|
||||
import GitClient from "@bp/service/git/git-client";
|
||||
import { inferSquash } from "@bp/service/git/git-util";
|
||||
import { GitPullRequest, BackportPullRequest, GitClientType } from "@bp/service/git/git.types";
|
||||
import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
|
||||
import { CommitSchema, MergeRequestSchema, UserSchema } from "@gitbeaker/rest";
|
||||
|
@ -45,10 +46,14 @@ export default class GitLabClient implements GitClient {
|
|||
// READ
|
||||
|
||||
// example: <host>/api/v4/projects/<namespace>%2Fbackporting-example/merge_requests/1
|
||||
async getPullRequest(namespace: string, repo: string, mrNumber: number, squash = true): Promise<GitPullRequest> {
|
||||
async getPullRequest(namespace: string, repo: string, mrNumber: number, squash: boolean | undefined): Promise<GitPullRequest> {
|
||||
const projectId = this.getProjectId(namespace, repo);
|
||||
const { data } = await this.client.get(`/projects/${projectId}/merge_requests/${mrNumber}`);
|
||||
|
||||
if (squash === undefined) {
|
||||
squash = inferSquash(data.state === "opened", data.squash_commit_sha);
|
||||
}
|
||||
|
||||
const commits: string[] = [];
|
||||
if (!squash) {
|
||||
// fetch all commits
|
||||
|
@ -65,7 +70,7 @@ export default class GitLabClient implements GitClient {
|
|||
return this.mapper.mapPullRequest(data as MergeRequestSchema, commits);
|
||||
}
|
||||
|
||||
getPullRequestFromUrl(mrUrl: string, squash = true): Promise<GitPullRequest> {
|
||||
getPullRequestFromUrl(mrUrl: string, squash: boolean | undefined): Promise<GitPullRequest> {
|
||||
const { namespace, project, id } = this.extractMergeRequestData(mrUrl);
|
||||
return this.getPullRequest(namespace, project, id, squash);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue