Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
33 changes: 33 additions & 0 deletions src/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
assertGitAvailable,
buildPathspecArgs,
ensureCommitAvailable,
extractBranchName,
Expand Down Expand Up @@ -745,3 +746,35 @@ describe("merge commit handling", () => {
});
});
});

describe("assertGitAvailable", () => {
it("succeeds inside a git repository with git on PATH", () => {
const repo = createTempRepo();
try {
expect(() => assertGitAvailable(repo.cwd)).not.toThrow();
} finally {
rmSync(repo.cwd, { recursive: true, force: true });
}
});

it("throws when not inside a git repository", () => {
const cwd = mkdtempSync(join(tmpdir(), "linear-release-no-repo-"));
try {
expect(() => assertGitAvailable(cwd)).toThrow(/git repository/);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
});

it("throws with a PATH hint when the git binary is missing", () => {
const repo = createTempRepo();
const originalPath = process.env.PATH;
process.env.PATH = "/nonexistent-linear-release-test-dir";
try {
expect(() => assertGitAvailable(repo.cwd)).toThrow(/git.*on PATH/);
} finally {
process.env.PATH = originalPath;
rmSync(repo.cwd, { recursive: true, force: true });
}
});
});
29 changes: 29 additions & 0 deletions src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,35 @@ export function buildPathspecArgs(includePaths: string[] | null): string {
return `-- ${patterns.join(" ")}`;
}

/**
* Verifies the runtime environment can satisfy the CLI's git requirements:
* 1. The `git` binary is on PATH.
* 2. The current working directory is inside a git repository.
*
* Call once at startup, before any other git operations, so cryptic
* downstream failures (ENOENT, "not a git repository") become useful
* diagnostics for CI users.
*/
export function assertGitAvailable(cwd: string = process.cwd()): void {
try {
execSync("git --version", {
cwd,
stdio: ["ignore", "ignore", "pipe"],
});
} catch {
throw new Error("linear-release requires `git` on PATH, but `git --version` failed. Install git in your CI image.");
Comment thread
RomainCscn marked this conversation as resolved.
Outdated
}

try {
execSync("git rev-parse --is-inside-work-tree", {
cwd,
stdio: ["ignore", "ignore", "pipe"],
});
} catch {
throw new Error("linear-release must run inside a git repository, but no `.git` directory was found.");
}
}

export function getCurrentGitInfo(cwd: string = process.cwd()): GitInfo {
try {
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
Expand Down
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { LinearClient, LinearClientOptions } from "@linear/sdk";
import { ensureCommitAvailable, getCommitContextsBetweenShas, getCurrentGitInfo, getRepoInfo } from "./git";
import {
assertGitAvailable,
ensureCommitAvailable,
getCommitContextsBetweenShas,
getCurrentGitInfo,
getRepoInfo,
} from "./git";
import { scanCommits } from "./scan";
import {
Release,
Expand Down Expand Up @@ -524,6 +530,8 @@ async function updateReleaseByPipeline(options: {
}

async function main() {
assertGitAvailable();

let result: {
release: { id: string; name: string; version?: string; url?: string };
} | null = null;
Expand Down
Loading