feat(git): support nested git repositories in the Git tab

When the project root is not itself a git repository, discover nested
repositories (depth- and visit-capped readdir walk via a new
/api/fs/git-dirs route), auto-select the first one, and show a repository
picker next to the branch dropdown to switch. Selections persist per
runtime and root; discovery failure is a distinct marker with a retry
action, never an empty success.
This commit is contained in:
jaygupta17
2026-08-08 19:08:42 +05:30
parent 4faf660a38
commit 9c9c28a552
20 changed files with 989 additions and 191 deletions
@@ -335,3 +335,61 @@ describe('useGitStore', () => {
expect(useGitStore.getState().getDirectoryState('/repo')?.status).toBe(initialStatus);
});
});
describe('useGitStore nested repository discovery', () => {
beforeEach(() => {
useGitStore.getState().resetForRuntimeSwitch(getRuntimeKey());
});
test('selects a nested repo per root and persists the selection', () => {
useGitStore.getState().selectNestedRepo('/root-a', '/root-a/repo-one');
expect(useGitStore.getState().nestedRepoSelection.get('/root-a')).toBe('/root-a/repo-one');
// Re-seeding from storage (as a page refresh would) restores the pick.
useGitStore.getState().resetForRuntimeSwitch(getRuntimeKey());
expect(useGitStore.getState().nestedRepoSelection.get('/root-a')).toBe('/root-a/repo-one');
});
test('keeps selections isolated per root', () => {
useGitStore.getState().selectNestedRepo('/root-a', '/root-a/one');
useGitStore.getState().selectNestedRepo('/root-b', '/root-b/two');
expect(useGitStore.getState().nestedRepoSelection.get('/root-a')).toBe('/root-a/one');
expect(useGitStore.getState().nestedRepoSelection.get('/root-b')).toBe('/root-b/two');
});
test('clears only the given root selection', () => {
useGitStore.getState().selectNestedRepo('/root-a', '/root-a/one');
useGitStore.getState().selectNestedRepo('/root-b', '/root-b/two');
useGitStore.getState().clearNestedRepoSelection('/root-a');
expect(useGitStore.getState().nestedRepoSelection.has('/root-a')).toBe(false);
expect(useGitStore.getState().nestedRepoSelection.get('/root-b')).toBe('/root-b/two');
});
test('runtime switch does not leak selections or discovery across runtimes', () => {
useGitStore.getState().selectNestedRepo('/root-a', '/root-a/one');
useGitStore.setState({ nestedReposByRoot: new Map([['/root-a', ['/root-a/one']]]) });
useGitStore.getState().resetForRuntimeSwitch('runtime-b');
expect(useGitStore.getState().nestedRepoSelection.size).toBe(0);
expect(useGitStore.getState().nestedReposByRoot.size).toBe(0);
});
test('marks discovery failure as a failed marker, not an empty success', async () => {
await useGitStore.getState().ensureNestedRepos('/root-a');
expect(useGitStore.getState().nestedReposByRoot.get('/root-a')).toBeNull();
});
test('dedupes concurrent discovery runs for the same root', async () => {
const first = useGitStore.getState().ensureNestedRepos('/root-a');
const second = useGitStore.getState().ensureNestedRepos('/root-a');
await Promise.all([first, second]);
expect(useGitStore.getState().nestedReposByRoot.get('/root-a')).toBeNull();
});
});