.git as a file (worktrees, submodules) isn't a standalone repo — the scanner was flooding the import list with worktree entries alongside their real repo.
74 lines
2.4 KiB
C#
74 lines
2.4 KiB
C#
namespace ClaudeDo.Ui.Services;
|
|
|
|
public sealed record RepoCandidate(string Name, string FullPath);
|
|
|
|
public static class RepoScanner
|
|
{
|
|
private const int MaxDepth = 5;
|
|
|
|
private static readonly HashSet<string> SkipDirNames = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"node_modules", "bin", "obj", ".git", ".vs", "packages",
|
|
};
|
|
|
|
public static IReadOnlyList<RepoCandidate> Scan(string parentFolder)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(parentFolder) || !Directory.Exists(parentFolder))
|
|
return Array.Empty<RepoCandidate>();
|
|
|
|
try
|
|
{
|
|
var info = new DirectoryInfo(parentFolder);
|
|
if (info.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
|
return Array.Empty<RepoCandidate>();
|
|
}
|
|
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
|
|
{ return Array.Empty<RepoCandidate>(); }
|
|
|
|
var result = new List<RepoCandidate>();
|
|
ScanDirectory(parentFolder, depth: 0, result);
|
|
return result;
|
|
}
|
|
|
|
private static void ScanDirectory(string dir, int depth, List<RepoCandidate> result)
|
|
{
|
|
if (IsRepo(dir))
|
|
{
|
|
result.Add(new RepoCandidate(Path.GetFileName(dir), dir));
|
|
return;
|
|
}
|
|
|
|
if (depth >= MaxDepth)
|
|
return;
|
|
|
|
IEnumerable<string> subdirs;
|
|
try { subdirs = Directory.EnumerateDirectories(dir); }
|
|
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
|
|
{ return; }
|
|
|
|
foreach (var subdir in subdirs)
|
|
{
|
|
var name = Path.GetFileName(subdir);
|
|
if (SkipDirNames.Contains(name))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
var info = new DirectoryInfo(subdir);
|
|
if (info.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
|
continue;
|
|
}
|
|
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
|
|
{ continue; }
|
|
|
|
try { ScanDirectory(subdir, depth + 1, result); }
|
|
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
// A real clone has .git as a directory. Worktrees and submodules have it as a file
|
|
// ("gitdir: ...") — those are not standalone repos and would flood the list.
|
|
private static bool IsRepo(string dir) => Directory.Exists(Path.Combine(dir, ".git"));
|
|
}
|