= ({
data-openbitfun-product-component="branch-quick-switch"
data-openbitfun-product-part="input"
/>
+ }
+ loading={isLoading}
+ disabled={isSwitching}
+ onClick={() => void loadBranches()}
+ />
+ {loadFailed && branches.length > 0 && (
+
+ {t('quickSwitch.errors.refreshFailed')}
+
+ )}
{isLoading && branches.length === 0 ? (
({
isGitRepository: vi.fn(),
getRepository: vi.fn(),
getStatus: vi.fn(),
+ getBranches: vi.fn(),
}));
const gitStateManagerMock = vi.hoisted(() => ({
@@ -30,6 +31,24 @@ vi.mock('../state/GitStateManager', () => ({
const repositoryPath = { workspaceId: 'workspace-1', repositoryPath: 'D:/workspace/OpenBitFun' };
+describe('GitService branch loading', () => {
+ it('preserves the legacy empty-list fallback but lets interactive callers receive errors', async () => {
+ const error = new Error('Remote host unavailable');
+ gitApiMocks.getBranches.mockRejectedValue(error);
+ await expect(gitService.getBranches(repositoryPath)).resolves.toEqual([]);
+ await expect(gitService.getBranches(repositoryPath, true, { throwOnError: true })).rejects.toBe(error);
+ });
+
+ it('forwards the workspace scope and adapts a successful strict read', async () => {
+ const scope = { workspaceId: 'ssh-workspace', repositoryPath: '/srv/project' };
+ gitApiMocks.getBranches.mockResolvedValue([{ name: 'main', current: true, remote: false }]);
+ await expect(gitService.getBranches(scope, true, { throwOnError: true })).resolves.toEqual([
+ expect.objectContaining({ name: 'main', current: true, ahead: 0, behind: 0 }),
+ ]);
+ expect(gitApiMocks.getBranches).toHaveBeenLastCalledWith(scope, true);
+ });
+});
+
describe('GitService dangerous operation refresh guard', () => {
beforeEach(() => {
vi.clearAllMocks();
diff --git a/src/web-ui/src/tools/git/services/GitService.ts b/src/web-ui/src/tools/git/services/GitService.ts
index ca86824d14..6328f1af5e 100644
--- a/src/web-ui/src/tools/git/services/GitService.ts
+++ b/src/web-ui/src/tools/git/services/GitService.ts
@@ -288,12 +288,18 @@ export class GitService {
}
}
- async getBranches(repositoryPath: GitWorkspaceScope, includeRemote: boolean = false): Promise {
+ async getBranches(
+ repositoryPath: GitWorkspaceScope,
+ includeRemote: boolean = false,
+ options: { throwOnError?: boolean } = {},
+ ): Promise {
try {
const result = await gitAPI.getBranches(repositoryPath, includeRemote);
return this.adaptBranches(result);
} catch (error) {
log.error('Failed to get branches', error);
+ // Interactive callers must distinguish a failed read from an empty repository.
+ if (options.throwOnError) throw error;
return [];
}
}