Add multi-repo workspace support#426
Conversation
When running roborev from a parent directory containing multiple git repos (e.g., a monorepo workspace), commands now gracefully handle not being in a git repo: `list` shows jobs across all child repos via repo_prefix filter, `show` suggests using job IDs, and `review` lists available child repos with --repo hints. Includes LIKE wildcard escaping for path safety. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change escapeLike to use '!' instead of '\' as the ESCAPE character in LIKE clauses. The backslash escape conflicted with Windows path separators stored in root_path, causing prefix filters to fail on Windows. Update all three LIKE ESCAPE clauses (ListJobs, CountJobStats, ListReposWithReviewCountsByPrefix). Also fix review hint paths to show full absolute paths instead of bare repo names, and use strings.Builder to satisfy the modernize linter. Add TestEscapeLike and TestPrefixFilterWithSpecialChars to verify correct escaping of !, %, and _ characters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace three near-identical ListReposWithReviewCounts* functions with a single ListReposWithReviewCounts(opts ...ListReposOption) using composable WithRepoPathPrefix and WithRepoBranch options. This matches the existing ListJobsOption pattern in jobs.go and enables combined prefix+branch filtering that was previously impossible (the if/else chain in handleListRepos made them mutually exclusive). Add filepath.Clean normalization for repo_prefix and prefix query params in handleListJobs and handleListRepos to handle trailing slashes and ".." segments. Add tests for combined prefix+branch filtering, trailing-slash normalization, and dot-dot path normalization. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When running from a non-git parent directory (workspace mode), the auto-detected branch was correctly suppressed since it makes no sense to filter by one repo's branch across a workspace. However, an explicit --branch flag was also being dropped. Check cmd.Flags().Changed to honor explicitly passed --branch values in workspace mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
findChildGitRepos checked info.IsDir() for .git, which missed git worktrees and submodules where .git is a file pointing to the actual git directory. Remove the IsDir() check — os.Stat succeeding is sufficient to confirm the entry is a git repo. The hint path fix (showing full absolute paths instead of bare repo names) was included in the earlier ESCAPE commit since it touched the same code. This commit adds the .git file detection fix and tests for both behaviors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#7920: Add CountJobStats and ListReposWithReviewCounts prefix tests with special chars (!, %, _) and backslash-in-path test verifying no SQL errors after the ESCAPE char change. #7922: Add dot-dot normalization test for /api/repos prefix endpoint. #7923: Add workspace-mode integration tests verifying auto-detected branch is suppressed and explicit --branch is honored. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete seeded jobs before asserting CountJobStats so the test validates actual prefix-filter behavior (2 done under prefix, not 3) instead of vacuously passing with all-zero stats from queued jobs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add filepath.ToSlash after filepath.Clean on repo_prefix and prefix query params in handleListJobs and handleListRepos, and on the client-side prefix in list.go. The SQL LIKE clause uses '/%' as the separator, so backslash-separated Windows paths from filepath.Abs or filepath.Clean would fail to match. Add tests for roborev show outside a git repo: default invocation returns a guidance error, explicit --job ID still works. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TestHandleListJobsSlashNormalization and TestHandleListReposSlashNormalization verifying that forward-slash prefixes (the output of filepath.ToSlash) correctly match stored repo paths through the handler pipeline. Note: filepath.ToSlash is already platform-conditional by design — it replaces os.PathSeparator with '/', which is a no-op on POSIX (where os.PathSeparator is already '/'). Backslashes in POSIX path segments are valid filename characters and are not transformed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
GetOrCreateRepo and GetRepoByPath now apply filepath.ToSlash after filepath.Abs so root_path is always stored with forward slashes, matching the LIKE '/%' pattern used by prefix filters. This fixes workspace prefix filtering on Windows where filepath.Abs returns backslash-separated paths. Strip trailing slashes from prefix options (WithRepoPrefix, WithRepoPathPrefix) so root-prefix "/" doesn't produce the double-slash LIKE pattern "//%". A root prefix trims to empty string, disabling the filter (all repos match), which is correct. Strengthen slash normalization test assertions to verify repo identities (names, paths) not just counts. Add TestRootPrefixMatchesAllRepos covering the "/" edge case. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RenameRepo was missing filepath.ToSlash after filepath.Abs, so on Windows the backslash path didn't match the forward-slash stored path. Tests used hardcoded Unix paths (/tmp/workspace, /ws, etc.) which on Windows get a drive prefix from filepath.Abs (C:/tmp/workspace), causing prefix filters to miss. Switch to t.TempDir()-based paths that are absolute on all platforms. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Normalize repo path params to forward slashes in handleListJobs and handleListBranches, matching the forward-slash paths stored by GetOrCreateRepo. Fix CI poller test harness to use repo.RootPath (normalized) instead of raw t.TempDir() path. Fix test assertions that compared forward-slash DB paths against native backslash paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
Summary
Adds workspace mode:
roborev listandroborev reviewnow work from non-git parent directories that contain multiple git repos. Runningroborev listfrom such a directory filters jobs/repos by path prefix, showing results across all child repositories.Changes
Workspace detection (
cmd/roborev/list.go,cmd/roborev/review.go)roborev listdetects when the working directory is not a git repo and no--repois specified — switches to workspace mode, sending the current directory as arepo_prefixquery paramroborev reviewfrom a non-git parent directory lists child repos containing.git(file or directory) and prints hint paths with full absolute paths--branchflags are always honored viacmd.Flags().Changed("branch")Composable repo query options (
internal/storage/repos.go)Replaced three near-identical functions (
ListReposWithReviewCounts,ListReposWithReviewCountsByPrefix,ListReposWithReviewCountsByBranch) with a single function using composable options:WithRepoPathPrefix(prefix)— filter repos by path prefixWithRepoBranch(branch)— filter repos by branch (switches from LEFT JOIN to INNER JOIN + HAVING)SQL ESCAPE character fix (
internal/storage/jobs.go,internal/storage/repos.go)Changed
escapeLikefrom\to!as the ESCAPE character. The backslash conflicted with Windows path separators stored inroot_path, causing invalid escape sequences in LIKE clauses.Cross-platform path normalization
GetOrCreateRepoandGetRepoByPathapplyfilepath.ToSlashat write time so DB rows always store forward-slash paths regardless of platformRenameRepoandFindReponormalize paths before queryingfilepath.ToSlash(filepath.Clean(...))to prefix query params, normalizing trailing slashes,..segments, and backslasheslist.go) normalizes the workspace prefix to forward slashes before sendingGit worktree detection (
cmd/roborev/review.go)findChildGitReposnow detects.gitas either a file (worktrees, submodules) or a directory (regular repos). Previously only checkedIsDir().Path normalization in server handlers (
internal/daemon/server.go)handleListJobsandhandleListReposapplyfilepath.Clean+filepath.ToSlashto prefix params, and pass both prefix and branch options when present (previously mutually exclusive via if/else chain).🤖 Generated with Claude Code