fix: made git more stable

This commit is contained in:
2026-02-22 11:10:54 +01:00
parent b166dd5a81
commit 78a0f0f62f
4 changed files with 540 additions and 32 deletions

View File

@@ -18,10 +18,11 @@ const mockPush = vi.fn();
const mockGetRemotes = vi.fn();
const mockAddRemote = vi.fn();
const mockRemote = vi.fn();
const { mockReadFile, mockStat, mockWriteFile } = vi.hoisted(() => ({
const { mockReadFile, mockStat, mockWriteFile, mockExecFile } = vi.hoisted(() => ({
mockReadFile: vi.fn(),
mockStat: vi.fn(),
mockWriteFile: vi.fn(),
mockExecFile: vi.fn(),
}));
vi.mock('fs/promises', () => ({
@@ -54,6 +55,13 @@ vi.mock('simple-git', () => ({
})),
}));
vi.mock('node:child_process', () => ({
execFile: mockExecFile,
default: {
execFile: mockExecFile,
},
}));
import { GitEngine } from '../../src/main/engine/GitEngine';
describe('GitEngine', () => {
@@ -85,6 +93,9 @@ describe('GitEngine', () => {
addRemote: mockAddRemote,
remote: mockRemote,
}));
mockExecFile.mockImplementation((_command, _args, _options, callback) => {
callback(null, '', '');
});
gitEngine = new GitEngine();
});
@@ -171,6 +182,29 @@ describe('GitEngine', () => {
{ path: 'staged.md', status: 'staged' },
]);
});
it('should recover status through CLI when status command fails with spawn EBADF', async () => {
mockStatus.mockRejectedValue(new Error('Error: spawn EBADF'));
mockExecFile.mockImplementation((command, args, _options, callback) => {
expect(command).toBe('git');
expect(args).toEqual(['status', '--porcelain=v1', '-z']);
callback(null, '?? new-file.md\0', '');
});
const result = await gitEngine.getStatus('/tmp/project');
expect(result).toEqual({
files: [{ path: 'new-file.md', status: 'untracked' }],
counts: {
untracked: 1,
modified: 0,
deleted: 0,
renamed: 0,
staged: 0,
total: 1,
},
});
});
});
describe('getDiff', () => {
@@ -465,6 +499,53 @@ describe('GitEngine', () => {
},
]);
});
it('should recover with CLI history when local log fails with spawn EBADF', async () => {
mockStatus.mockResolvedValue({ current: 'main', tracking: 'origin/main' });
mockLog.mockRejectedValueOnce(new Error('Error: spawn EBADF'));
mockExecFile.mockImplementation((command, args, _options, callback) => {
expect(command).toBe('git');
const normalizedArgs = Array.isArray(args) ? args : [];
if (normalizedArgs[0] === 'log' && normalizedArgs.includes('--max-count') && normalizedArgs[normalizedArgs.length - 1] === '20') {
const local = 'abc123def456\x1f2026-02-16T10:00:00.000Z\x1ffeat: local via cli\x1fLocal Dev\x1e';
callback(null, local, '');
return;
}
if (normalizedArgs[0] === 'rev-parse') {
callback(null, 'origin/main\n', '');
return;
}
if (normalizedArgs[0] === 'rev-list') {
callback(null, '0\n', '');
return;
}
if (normalizedArgs[0] === 'log' && normalizedArgs[normalizedArgs.length - 1] === 'origin/main') {
callback(null, '', '');
return;
}
callback(new Error(`Unexpected git args: ${normalizedArgs.join(' ')}`), '', '');
});
const result = await gitEngine.getHistory('/tmp/project', 20);
expect(result).toEqual([
{
hash: 'abc123def456',
shortHash: 'abc123d',
date: '2026-02-16T10:00:00.000Z',
subject: 'feat: local via cli',
author: 'Local Dev',
syncStatus: 'local-only',
},
]);
expect(mockExecFile).toHaveBeenCalled();
});
});
describe('getFileHistory', () => {

View File

@@ -750,7 +750,7 @@ describe('GitSidebar', () => {
fireEvent.click(fetchButton);
});
expect(await screen.findByText(/authentication required/i)).toBeInTheDocument();
expect((await screen.findAllByText(/authentication required/i)).length).toBeGreaterThan(0);
expect(screen.getAllByText(/create a github personal access token/i)).toHaveLength(1);
expect(screen.getByText(/retry with username \+ token/i)).toBeInTheDocument();
});
@@ -872,8 +872,8 @@ describe('GitSidebar', () => {
await Promise.resolve();
});
expect((window as any).electronAPI.git.fetch).toHaveBeenCalledTimes(1);
expect((window as any).electronAPI.git.getRemoteState).toHaveBeenCalledTimes(1);
expect((window as any).electronAPI.git.fetch).toHaveBeenCalledTimes(0);
await act(async () => {
vi.advanceTimersByTime(30000);
@@ -881,7 +881,7 @@ describe('GitSidebar', () => {
await Promise.resolve();
});
expect((window as any).electronAPI.git.fetch).toHaveBeenCalledTimes(1);
expect((window as any).electronAPI.git.fetch).toHaveBeenCalledTimes(2);
expect((window as any).electronAPI.git.getRemoteState).toHaveBeenCalledTimes(2);
} finally {
vi.useRealTimers();