diff --git a/src/ClaudeDo.Ui/Services/RepoScanner.cs b/src/ClaudeDo.Ui/Services/RepoScanner.cs new file mode 100644 index 0000000..bf3a723 --- /dev/null +++ b/src/ClaudeDo.Ui/Services/RepoScanner.cs @@ -0,0 +1,25 @@ +namespace ClaudeDo.Ui.Services; + +public sealed record RepoCandidate(string Name, string FullPath); + +public static class RepoScanner +{ + public static IReadOnlyList Scan(string parentFolder) + { + if (string.IsNullOrWhiteSpace(parentFolder) || !Directory.Exists(parentFolder)) + return Array.Empty(); + + var result = new List(); + IEnumerable subdirs; + try { subdirs = Directory.EnumerateDirectories(parentFolder); } + catch { return Array.Empty(); } + + foreach (var dir in subdirs) + { + var gitPath = Path.Combine(dir, ".git"); + if (Directory.Exists(gitPath) || File.Exists(gitPath)) + result.Add(new RepoCandidate(Path.GetFileName(dir), dir)); + } + return result; + } +} diff --git a/tests/ClaudeDo.Ui.Tests/RepoScannerTests.cs b/tests/ClaudeDo.Ui.Tests/RepoScannerTests.cs new file mode 100644 index 0000000..d3e88aa --- /dev/null +++ b/tests/ClaudeDo.Ui.Tests/RepoScannerTests.cs @@ -0,0 +1,78 @@ +using ClaudeDo.Ui.Services; + +namespace ClaudeDo.Ui.Tests; + +public sealed class RepoScannerTests : IDisposable +{ + private readonly string _root = + Path.Combine(Path.GetTempPath(), "repo-scan-" + Guid.NewGuid().ToString("N")); + + public RepoScannerTests() => Directory.CreateDirectory(_root); + + public void Dispose() + { + try { Directory.Delete(_root, recursive: true); } catch { } + } + + private string MakeDir(string name) + { + var p = Path.Combine(_root, name); + Directory.CreateDirectory(p); + return p; + } + + [Fact] + public void Scan_ReturnsSubfoldersWithGitDirectory() + { + var repo = MakeDir("repo-a"); + Directory.CreateDirectory(Path.Combine(repo, ".git")); + + var result = RepoScanner.Scan(_root); + + Assert.Single(result); + Assert.Equal("repo-a", result[0].Name); + Assert.Equal(repo, result[0].FullPath); + } + + [Fact] + public void Scan_TreatsDotGitFileAsRepo() + { + var repo = MakeDir("worktree-repo"); + File.WriteAllText(Path.Combine(repo, ".git"), "gitdir: ../somewhere"); + + var result = RepoScanner.Scan(_root); + + Assert.Single(result); + Assert.Equal("worktree-repo", result[0].Name); + } + + [Fact] + public void Scan_IgnoresPlainFolders() + { + MakeDir("not-a-repo"); + + var result = RepoScanner.Scan(_root); + + Assert.Empty(result); + } + + [Fact] + public void Scan_IsNotRecursive() + { + var nested = MakeDir(Path.Combine("outer", "inner")); + Directory.CreateDirectory(Path.Combine(nested, ".git")); + // outer itself has no .git + + var result = RepoScanner.Scan(_root); + + Assert.Empty(result); + } + + [Fact] + public void Scan_ReturnsEmptyForMissingFolder() + { + var result = RepoScanner.Scan(Path.Combine(_root, "does-not-exist")); + + Assert.Empty(result); + } +}