2026-03-06 17:22:59 +02:00
|
|
|
import React from 'react';
|
|
|
|
|
import { checkIsGitRepository } from '@/lib/gitApi';
|
2026-03-31 18:47:00 +03:00
|
|
|
import { mapWithConcurrency } from '@/lib/concurrency';
|
2026-03-06 17:22:59 +02:00
|
|
|
import { getRootBranch } from '@/lib/worktrees/worktreeStatus';
|
|
|
|
|
|
|
|
|
|
type Project = { id: string; path: string; normalizedPath: string };
|
|
|
|
|
|
|
|
|
|
type DirectoryState = { status?: { current?: string | null } | null };
|
|
|
|
|
|
|
|
|
|
type Args = {
|
|
|
|
|
projects: Array<{ id: string; path: string }>;
|
|
|
|
|
normalizedProjects: Project[];
|
|
|
|
|
normalizePath: (value?: string | null) => string | null;
|
|
|
|
|
gitDirectories: Map<string, DirectoryState>;
|
|
|
|
|
setProjectRepoStatus: React.Dispatch<React.SetStateAction<Map<string, boolean | null>>>;
|
|
|
|
|
setProjectRootBranches: React.Dispatch<React.SetStateAction<Map<string, string>>>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useProjectRepoStatus = (args: Args): void => {
|
|
|
|
|
const {
|
|
|
|
|
projects,
|
|
|
|
|
normalizedProjects,
|
|
|
|
|
normalizePath,
|
|
|
|
|
gitDirectories,
|
|
|
|
|
setProjectRepoStatus,
|
|
|
|
|
setProjectRootBranches,
|
|
|
|
|
} = args;
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
let cancelled = false;
|
|
|
|
|
const normalized = projects
|
|
|
|
|
.map((project) => ({ id: project.id, path: normalizePath(project.path) }))
|
|
|
|
|
.filter((project): project is { id: string; path: string } => Boolean(project.path));
|
|
|
|
|
|
|
|
|
|
setProjectRepoStatus(new Map());
|
|
|
|
|
|
|
|
|
|
if (normalized.length === 0) {
|
|
|
|
|
return () => {
|
|
|
|
|
cancelled = true;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 18:47:00 +03:00
|
|
|
void mapWithConcurrency(normalized, 2, async (project) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await checkIsGitRepository(project.path);
|
|
|
|
|
if (!cancelled) {
|
|
|
|
|
setProjectRepoStatus((prev) => {
|
|
|
|
|
const next = new Map(prev);
|
|
|
|
|
next.set(project.id, result);
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
if (!cancelled) {
|
|
|
|
|
setProjectRepoStatus((prev) => {
|
|
|
|
|
const next = new Map(prev);
|
|
|
|
|
next.set(project.id, null);
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-06 17:22:59 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
cancelled = true;
|
|
|
|
|
};
|
|
|
|
|
}, [normalizePath, projects, setProjectRepoStatus]);
|
|
|
|
|
|
|
|
|
|
const projectGitBranchesKey = React.useMemo(() => {
|
|
|
|
|
return normalizedProjects
|
|
|
|
|
.map((project) => {
|
|
|
|
|
const dirState = gitDirectories.get(project.normalizedPath);
|
|
|
|
|
return `${project.id}:${dirState?.status?.current ?? ''}`;
|
|
|
|
|
})
|
|
|
|
|
.join('|');
|
|
|
|
|
}, [normalizedProjects, gitDirectories]);
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
let cancelled = false;
|
|
|
|
|
const run = async () => {
|
2026-03-31 18:47:00 +03:00
|
|
|
const entries = await mapWithConcurrency(normalizedProjects, 2, async (project) => {
|
|
|
|
|
const branch = await getRootBranch(project.normalizedPath).catch(() => null);
|
|
|
|
|
return { id: project.id, branch };
|
|
|
|
|
});
|
2026-03-06 17:22:59 +02:00
|
|
|
if (cancelled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setProjectRootBranches((prev) => {
|
|
|
|
|
const next = new Map(prev);
|
|
|
|
|
entries.forEach(({ id, branch }) => {
|
|
|
|
|
if (branch) {
|
|
|
|
|
next.set(id, branch);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
void run();
|
|
|
|
|
return () => {
|
|
|
|
|
cancelled = true;
|
|
|
|
|
};
|
|
|
|
|
}, [normalizedProjects, projectGitBranchesKey, setProjectRootBranches]);
|
|
|
|
|
};
|