Skip to content

Commit ef79345

Browse files
authored
Ensure git is available (#57)
1 parent cd1e730 commit ef79345

3 files changed

Lines changed: 73 additions & 1 deletion

File tree

src/git.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
44
import { join } from "node:path";
55
import { afterAll, beforeAll, describe, expect, it } from "vitest";
66
import {
7+
assertGitAvailable,
78
buildPathspecArgs,
89
ensureCommitAvailable,
910
extractBranchName,
@@ -745,3 +746,35 @@ describe("merge commit handling", () => {
745746
});
746747
});
747748
});
749+
750+
describe("assertGitAvailable", () => {
751+
it("succeeds inside a git repository with git on PATH", () => {
752+
const repo = createTempRepo();
753+
try {
754+
expect(() => assertGitAvailable(repo.cwd)).not.toThrow();
755+
} finally {
756+
rmSync(repo.cwd, { recursive: true, force: true });
757+
}
758+
});
759+
760+
it("throws when not inside a git repository", () => {
761+
const cwd = mkdtempSync(join(tmpdir(), "linear-release-no-repo-"));
762+
try {
763+
expect(() => assertGitAvailable(cwd)).toThrow(/git repository/);
764+
} finally {
765+
rmSync(cwd, { recursive: true, force: true });
766+
}
767+
});
768+
769+
it("throws with a PATH hint when the git binary is missing", () => {
770+
const repo = createTempRepo();
771+
const originalPath = process.env.PATH;
772+
process.env.PATH = "/nonexistent-linear-release-test-dir";
773+
try {
774+
expect(() => assertGitAvailable(repo.cwd)).toThrow(/git.*on PATH/);
775+
} finally {
776+
process.env.PATH = originalPath;
777+
rmSync(repo.cwd, { recursive: true, force: true });
778+
}
779+
});
780+
});

src/git.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,37 @@ export function buildPathspecArgs(includePaths: string[] | null): string {
3030
return `-- ${patterns.join(" ")}`;
3131
}
3232

33+
/**
34+
* Verifies the runtime environment can satisfy the CLI's git requirements:
35+
* 1. The `git` binary is on PATH.
36+
* 2. The current working directory is inside a git repository.
37+
*
38+
* Call once at startup, before any other git operations, so cryptic
39+
* downstream failures (ENOENT, "not a git repository") become useful
40+
* diagnostics for CI users.
41+
*/
42+
export function assertGitAvailable(cwd: string = process.cwd()): void {
43+
try {
44+
execSync("git --version", {
45+
cwd,
46+
stdio: ["ignore", "ignore", "pipe"],
47+
});
48+
} catch {
49+
throw new Error(
50+
"linear-release requires `git` on PATH, but `git --version` failed. Please make sure that git is installed and available.",
51+
);
52+
}
53+
54+
try {
55+
execSync("git rev-parse --is-inside-work-tree", {
56+
cwd,
57+
stdio: ["ignore", "ignore", "pipe"],
58+
});
59+
} catch {
60+
throw new Error("linear-release must run inside a git repository, but no `.git` directory was found.");
61+
}
62+
}
63+
3364
export function getCurrentGitInfo(cwd: string = process.cwd()): GitInfo {
3465
try {
3566
const branch = execSync("git rev-parse --abbrev-ref HEAD", {

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { LinearClient, LinearClientOptions } from "@linear/sdk";
2-
import { ensureCommitAvailable, getCommitContextsBetweenShas, getCurrentGitInfo, getRepoInfo } from "./git";
2+
import {
3+
assertGitAvailable,
4+
ensureCommitAvailable,
5+
getCommitContextsBetweenShas,
6+
getCurrentGitInfo,
7+
getRepoInfo,
8+
} from "./git";
39
import { scanCommits } from "./scan";
410
import {
511
Release,
@@ -524,6 +530,8 @@ async function updateReleaseByPipeline(options: {
524530
}
525531

526532
async function main() {
533+
assertGitAvailable();
534+
527535
let result: {
528536
release: { id: string; name: string; version?: string; url?: string };
529537
} | null = null;

0 commit comments

Comments
 (0)