Skip to content

Commit

Permalink
Merge pull request #374 from korthout/test-git
Browse files Browse the repository at this point in the history
Test git module
  • Loading branch information
korthout authored Aug 12, 2023
2 parents a9a5c1b + 7a46905 commit a397c99
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 74 deletions.
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`,
);
});
});
});

0 comments on commit a397c99

Please sign in to comment.