Skip to content
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
3 changes: 2 additions & 1 deletion apps/server/src/git/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import { detachStackFrame } from "./detachStackFrame.ts";
import * as ServerSettings from "../serverSettings.ts";
import type { GitManagerServiceError } from "@t3tools/contracts";
import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import { isNonRepositoryGitStderr } from "../vcs/GitVcsDriverCore.ts";
import * as SourceControlProvider from "@t3tools/source-control-core/server/SourceControlProvider";
import * as SourceControlRateLimit from "@t3tools/source-control-core/server/SourceControlRateLimit";
import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
Expand Down Expand Up @@ -191,7 +192,7 @@ type GitActionProgressPayload = StripProgressContext<GitActionProgressEvent>;
type GitActionProgressEmitter = (event: GitActionProgressPayload) => Effect.Effect<void, never>;

function isNotGitRepositoryError(error: GitCommandError): boolean {
return error.message.toLowerCase().includes("not a git repository");
return isNonRepositoryGitStderr(error.message);
}

interface OpenPrInfo {
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/storageCleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import * as Settings from "./serverSettings.ts";
import * as TerminalManager from "./terminal/Manager.ts";
import { isFilesystemRoot, managedWorktreesDirectories } from "./worktreesDirectory.ts";
import * as GitVcsDriver from "./vcs/GitVcsDriver.ts";
import { isNonRepositoryGitStderr } from "./vcs/GitVcsDriverCore.ts";
import { removeLinkedWorktree } from "./vcs/worktreeRemoval.ts";
import { withWorkspaceLease } from "./workspace/workspaceLease.ts";
import * as HostProcess from "@t3tools/shared/HostProcess";
Expand Down Expand Up @@ -618,7 +619,7 @@ const make = Effect.gen(function* () {
);
if (result === null) return null;
if (result.exitCode === 0 || result.exitCode === allowedExitCode) return result;
if (result.stderr.toLowerCase().includes("not a git repository")) return null;
if (isNonRepositoryGitStderr(result.stderr)) return null;
return yield* failure(
result.stderr.trim().split(/\r?\n/)[0]?.slice(0, 1000) || "command failed",
);
Expand Down
22 changes: 22 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
import { symlinksSupported } from "@t3tools/shared/testing/symlinks";
import * as ServerConfig from "../config.ts";
import {
isNonRepositoryGitStderr,
makeGitVcsDriverCore,
parseGitCheckoutProgressLine,
splitNullSeparatedGitStdoutPaths,
Expand Down Expand Up @@ -130,6 +131,27 @@ const makeDriverReleasingLock = (
return { driver, failures };
});

describe("isNonRepositoryGitStderr", () => {
it.each([
"fatal: not a git repository (or any of the parent directories): .git",
"fatal: not a git repository: /missing-git-directory",
// Git 2.56+ wording for a `.git` gitfile that points at a missing directory.
"fatal: gitfile does not point to a valid repository: /tmp/worktrees/feature/.git",
"FATAL: GITFILE DOES NOT POINT TO A VALID REPOSITORY: /tmp/x/.git",
])("classifies %j as a non-repository", (stderr) => {
assert.isTrue(isNonRepositoryGitStderr(stderr));
});

it.each([
"",
"fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.",
"fatal: Unable to create '/repo/.git/index.lock': File exists.",
"fatal: invalid gitfile format: /repo/.git",
])("does not classify %j as a non-repository", (stderr) => {
assert.isFalse(isNonRepositoryGitStderr(stderr));
});
});

it.effect("runs background Git in its own lane so user commands never queue behind it", () =>
Effect.gen(function* () {
const gate = yield* Deferred.make<void>();
Expand Down
12 changes: 10 additions & 2 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,16 @@ function isMissingGitCwdError(error: GitCommandError): boolean {
);
}

function isNonRepositoryGitStderr(stderr: string): boolean {
return stderr.toLowerCase().includes("not a git repository");
// Git 2.56+ reports a `.git` gitfile pointing nowhere as "gitfile does not point to a valid
// repository"; older releases said "not a git repository". Both mean the cwd is not a repository.
const NON_REPOSITORY_STDERR_PHRASES = [
"not a git repository",
"gitfile does not point to a valid repository",
] as const;

export function isNonRepositoryGitStderr(stderr: string): boolean {
const normalized = stderr.toLowerCase();
return NON_REPOSITORY_STDERR_PHRASES.some((phrase) => normalized.includes(phrase));
}
function isUnbornHeadStderr(stderr: string): boolean {
const normalized = stderr.toLowerCase();
Expand Down
Loading