fix: git errors on startup / check

This commit is contained in:
2026-02-22 10:30:58 +01:00
parent f43e150668
commit 2b5b992904
2 changed files with 58 additions and 10 deletions

View File

@@ -392,6 +392,11 @@ export class GitEngine {
return normalized.includes('no upstream branch') || normalized.includes('has no upstream branch'); return normalized.includes('no upstream branch') || normalized.includes('has no upstream branch');
} }
private isSpawnBadFileDescriptorError(message: string): boolean {
const normalized = message.toLowerCase();
return normalized.includes('spawn ebadf');
}
private async getCurrentBranchName(git: ReturnType<typeof simpleGit>): Promise<string | null> { private async getCurrentBranchName(git: ReturnType<typeof simpleGit>): Promise<string | null> {
try { try {
const status = await git.status(); const status = await git.status();
@@ -717,12 +722,11 @@ export class GitEngine {
} }
async getHistory(projectPath: string, limit = 20): Promise<GitHistoryEntry[]> { async getHistory(projectPath: string, limit = 20): Promise<GitHistoryEntry[]> {
const git = simpleGit(projectPath); const git = this.createNonInteractiveGit(projectPath);
const status = await git.status(); const status = await git.status();
const localHistory = await git.log({ maxCount: limit }); const localHistory = await git.log({ maxCount: limit });
if (!status.tracking) { const mapLocalHistory = (): GitHistoryEntry[] => localHistory.all.map((entry) => ({
return localHistory.all.map((entry) => ({
hash: entry.hash, hash: entry.hash,
shortHash: entry.hash.slice(0, 7), shortHash: entry.hash.slice(0, 7),
date: entry.date, date: entry.date,
@@ -730,11 +734,24 @@ export class GitEngine {
author: entry.author_name, author: entry.author_name,
syncStatus: 'local-only', syncStatus: 'local-only',
})); }));
if (!status.tracking) {
return mapLocalHistory();
} }
const behindCount = typeof status.behind === 'number' ? status.behind : Number(status.behind ?? 0); const behindCount = typeof status.behind === 'number' ? status.behind : Number(status.behind ?? 0);
const remoteHistoryLimit = Math.max(limit, limit + Math.max(behindCount, 0)); const remoteHistoryLimit = Math.max(limit, limit + Math.max(behindCount, 0));
const remoteHistory = await git.log([status.tracking, '--max-count', String(remoteHistoryLimit)]); let remoteHistory;
try {
remoteHistory = await git.log([status.tracking, '--max-count', String(remoteHistoryLimit)]);
} catch (error) {
const message = error instanceof Error ? error.message : String(error ?? '');
if (this.isSpawnBadFileDescriptorError(message)) {
return mapLocalHistory();
}
throw error;
}
type CommitLike = { type CommitLike = {
hash: string; hash: string;

View File

@@ -434,6 +434,37 @@ describe('GitEngine', () => {
}, },
]); ]);
}); });
it('should fall back to local history when remote history lookup fails with spawn EBADF', async () => {
mockStatus.mockResolvedValue({ current: 'main', tracking: 'origin/main' });
mockLog
.mockResolvedValueOnce({
all: [
{
hash: 'abc123def456',
date: '2026-02-16T10:00:00.000Z',
message: 'feat: local commit',
author_name: 'Local Dev',
},
],
})
.mockRejectedValueOnce(new Error('Error: spawn EBADF'));
const result = await gitEngine.getHistory('/tmp/project', 20);
expect(mockLog).toHaveBeenNthCalledWith(1, { maxCount: 20 });
expect(mockLog).toHaveBeenNthCalledWith(2, ['origin/main', '--max-count', '20']);
expect(result).toEqual([
{
hash: 'abc123def456',
shortHash: 'abc123d',
date: '2026-02-16T10:00:00.000Z',
subject: 'feat: local commit',
author: 'Local Dev',
syncStatus: 'local-only',
},
]);
});
}); });
describe('getFileHistory', () => { describe('getFileHistory', () => {