Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test git module #374

Merged
merged 5 commits into from
Aug 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/backport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import dedent from "dedent";

import { CreatePullRequestResponse, PullRequest } from "./github";
import { GithubApi } from "./github";
import * as git from "./git";
import { Git, GitRefNotFoundError } from "./git";
import * as utils from "./utils";

type PRContent = {
Expand Down Expand Up @@ -32,10 +32,12 @@ enum Output {
export class Backport {
private github;
private config;
private git;

constructor(github: GithubApi, config: Config) {
constructor(github: GithubApi, config: Config, git: Git) {
this.github = github;
this.config = config;
this.git = git;
}

async run(): Promise<void> {
Expand Down Expand Up @@ -70,7 +72,7 @@ export class Backport {
console.log(
`Fetching all the commits from the pull request: ${mainpr.commits + 1}`,
);
await git.fetch(
await this.git.fetch(
`refs/pull/${pull_number}/head`,
this.config.pwd,
mainpr.commits + 1, // +1 in case this concerns a shallowly cloned repo
Expand Down Expand Up @@ -104,9 +106,9 @@ export class Backport {
console.log(`Backporting to target branch '${target}...'`);

try {
await git.fetch(target, this.config.pwd, 1);
await this.git.fetch(target, this.config.pwd, 1);
} catch (error) {
if (error instanceof git.GitRefNotFoundError) {
if (error instanceof GitRefNotFoundError) {
const message = this.composeMessageForFetchTargetFailure(error.ref);
console.error(message);
successByTarget.set(target, false);
Expand All @@ -127,7 +129,11 @@ export class Backport {

console.log(`Start backport to ${branchname}`);
try {
await git.checkout(branchname, `origin/${target}`, this.config.pwd);
await this.git.checkout(
branchname,
`origin/${target}`,
this.config.pwd,
);
} catch (error) {
const message = this.composeMessageForBackportScriptFailure(
target,
Expand All @@ -148,7 +154,7 @@ export class Backport {
}

try {
await git.cherryPick(commitShas, this.config.pwd);
await this.git.cherryPick(commitShas, this.config.pwd);
} catch (error) {
const message = this.composeMessageForBackportScriptFailure(
target,
Expand All @@ -169,7 +175,7 @@ export class Backport {
}

console.info(`Push branch to origin`);
const pushExitCode = await git.push(branchname, this.config.pwd);
const pushExitCode = await this.git.push(branchname, this.config.pwd);
if (pushExitCode != 0) {
const message = this.composeMessageForGitPushFailure(
target,
Expand Down
128 changes: 68 additions & 60 deletions src/git.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execa } from "execa";
export type Execa = (typeof import("execa"))["execa"];

export class GitRefNotFoundError extends Error {
ref: string;
Expand All @@ -8,71 +8,79 @@ export class GitRefNotFoundError extends Error {
}
}

/**
* Fetches a ref from origin
*
* @param ref the sha, branchname, etc to fetch
* @param pwd the root of the git repository
* @param depth the number of commits to fetch
* @throws GitRefNotFoundError when ref not found
* @throws Error for any other non-zero exit code
*/
export async function fetch(ref: string, pwd: string, depth: number) {
const { exitCode } = await git(
"fetch",
[`--depth=${depth}`, "origin", ref],
pwd,
);
if (exitCode === 128) {
throw new GitRefNotFoundError(
`Expected to fetch '${ref}', but couldn't find it`,
ref,
);
} else if (exitCode !== 0) {
throw new Error(
`'git fetch origin ${ref}' failed with exit code ${exitCode}`,
);
}
}
export class Git {
constructor(private execa: Execa) {}

export async function push(branchname: string, pwd: string) {
const { exitCode } = await git(
"push",
["--set-upstream", "origin", branchname],
pwd,
);
return exitCode;
}
private async git(command: string, args: string[], pwd: string) {
console.log(`git ${command} ${args.join(" ")}`);
const child = this.execa("git", [command, ...args], {
cwd: pwd,
env: {
GIT_COMMITTER_NAME: "github-actions[bot]",
GIT_COMMITTER_EMAIL: "github-actions[bot]@users.noreply.github.com",
},
reject: false,
});
child.stderr?.pipe(process.stderr);
return child;
}

async function git(command: string, args: string[], pwd: string) {
console.log(`git ${command} ${args.join(" ")}`);
const child = execa("git", [command, ...args], {
cwd: pwd,
env: {
GIT_COMMITTER_NAME: "github-actions[bot]",
GIT_COMMITTER_EMAIL: "github-actions[bot]@users.noreply.github.com",
},
reject: false,
});
child.stderr?.pipe(process.stderr);
return child;
}
/**
* Fetches a ref from origin
*
* @param ref the sha, branchname, etc to fetch
* @param pwd the root of the git repository
* @param depth the number of commits to fetch
* @throws GitRefNotFoundError when ref not found
* @throws Error for any other non-zero exit code
*/
public async fetch(ref: string, pwd: string, depth: number) {
const { exitCode } = await this.git(
"fetch",
[`--depth=${depth}`, "origin", ref],
pwd,
);
if (exitCode === 128) {
throw new GitRefNotFoundError(
`Expected to fetch '${ref}', but couldn't find it`,
ref,
);
} else if (exitCode !== 0) {
throw new Error(
`'git fetch origin ${ref}' failed with exit code ${exitCode}`,
);
}
}

export async function checkout(branch: string, start: string, pwd: string) {
const { exitCode } = await git("switch", ["-c", branch, start], pwd);
if (exitCode !== 0) {
throw new Error(
`'git switch -c ${branch} ${start}' failed with exit code ${exitCode}`,
public async push(branchname: string, pwd: string) {
const { exitCode } = await this.git(
"push",
["--set-upstream", "origin", branchname],
pwd,
);
return exitCode;
}

public async checkout(branch: string, start: string, pwd: string) {
const { exitCode } = await this.git("switch", ["-c", branch, start], pwd);
if (exitCode !== 0) {
throw new Error(
`'git switch -c ${branch} ${start}' failed with exit code ${exitCode}`,
);
}
}
}

export async function cherryPick(commitShas: string[], pwd: string) {
const { exitCode } = await git("cherry-pick", ["-x", ...commitShas], pwd);
if (exitCode !== 0) {
await git("cherry-pick", ["--abort"], pwd);
throw new Error(
`'git cherry-pick -x ${commitShas}' failed with exit code ${exitCode}`,
public async cherryPick(commitShas: string[], pwd: string) {
const { exitCode } = await this.git(
"cherry-pick",
["-x", ...commitShas],
pwd,
);
if (exitCode !== 0) {
await this.git("cherry-pick", ["--abort"], pwd);
throw new Error(
`'git cherry-pick -x ${commitShas}' failed with exit code ${exitCode}`,
);
}
}
}
8 changes: 6 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as core from "@actions/core";
import { Backport } from "./backport";
import { Github } from "./github";
import { Git } from "./git";
import { execa } from "execa";

/**
* Called from the action.yml.
Expand All @@ -17,15 +19,17 @@ async function run(): Promise<void> {
const target_branches = core.getInput("target_branches");

const github = new Github(token);
const backport = new Backport(github, {
const git = new Git(execa);
const config = {
pwd,
labels: { pattern: pattern === "" ? undefined : new RegExp(pattern) },
pull: { description, title },
copy_labels_pattern:
copy_labels_pattern === "" ? undefined : new RegExp(copy_labels_pattern),
target_branches:
target_branches === "" ? undefined : (target_branches as string),
});
};
const backport = new Backport(github, config, git);

return backport.run();
}
Expand Down
4 changes: 0 additions & 4 deletions src/test/backport.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { findTargetBranches } from "../backport";

jest.mock("execa", () => ({
execa: jest.fn(),
}));

const default_pattern = /^backport ([^ ]+)$/;

describe("find target branches", () => {
Expand Down
46 changes: 46 additions & 0 deletions src/test/git.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Git, GitRefNotFoundError } from "../git";
import { execa } from "execa";

const git = new Git(execa);
let response = { exitCode: 0, stdout: "" };

jest.mock("execa", () => ({
execa: jest.fn(() => response),
}));

describe("git.fetch", () => {
describe("throws GitRefNotFoundError", () => {
it("when fetching an unknown ref, i.e. exit code 128", async () => {
response.exitCode = 128;
expect.assertions(3);
await git.fetch("unknown", "", 1).catch((error) => {
expect(error).toBeInstanceOf(GitRefNotFoundError);
expect(error).toHaveProperty(
"message",
"Expected to fetch 'unknown', but couldn't find it",
);
expect(error).toHaveProperty("ref", "unknown");
});
});
});

describe("throws Error", () => {
it("when failing with an unexpected non-zero exit code", async () => {
response.exitCode = 1;
await expect(git.fetch("unknown", "", 1)).rejects.toThrowError(
`'git fetch origin unknown' failed with exit code 1`,
);
});
});
});

describe("git.cherryPick", () => {
describe("throws Error", () => {
it("when failing with an unexpected non-zero exit code", async () => {
response.exitCode = 1;
await expect(git.cherryPick(["unknown"], "")).rejects.toThrowError(
`'git cherry-pick -x unknown' failed with exit code 1`,
);
});
});
});
Loading