Skip to content

git repo discovery: root walk, worktree dedup #12

Description

@Saber5656

Title

git repo discovery: root walk, worktree dedup

Summary

Implement repository discovery: depth-limited walk of configured roots,
.git dir/file detection, exclusion rules, symlink safety, and worktree
deduplication via git rev-parse --git-common-dir (DESIGN.md §7.2
"Discovery").

Context

The user's environment keeps repos under ~/dev and uses git worktrees
heavily (1 task = 1 worktree policy). Naive discovery would list a main repo
and each of its worktrees as separate repos and duplicate every commit in
reports. The common-dir dedup rule here is what prevents that.

Scope

  • internal/source/gitsrc/discover.go + tests (fixture repos built in
    t.TempDir() by test helpers).

Detailed Requirements

  1. type Repo struct { WorkDir string; CommonDir string; Name string }
    Name = base name of the canonical work dir.
  2. Discover(ctx context.Context, cfg config.SourcesGit, runner Runner) ([]Repo, []model.Warning):
    • Walk each root from cfg.GitRoots() (tilde-expanded by config
      accessors): breadth-first, max depth cfg.MaxDepth (root itself =
      depth 0).
    • Skip (do not descend into): directories whose base name is in
      exclude_dirs; all dot-directories (.foo) except that a .git
      entry marks its parent as a repo; symlinked directories (Lstat
      check — never follow, cycle prevention).
    • Repo detection: directory containing .git (dir or file). On
      detection, record candidate and do NOT descend further into it.
    • Missing/non-directory root → warning git_root_missing (already part
      of the canonical DESIGN §13 taxonomy; the constant exists in
      internal/model from issue 03).
    • Explicit cfg.Repos paths join the candidate list (missing path →
      git_root_missing warning naming it).
  3. Dedup: for each candidate run
    git --no-optional-locks -C <dir> rev-parse --path-format=absolute --git-common-dir via the injected Runner (interface
    Run(ctx, dir string, args ...string) (stdout string, err error) — real
    impl in issue 12, test impl fake). Canonical key = cleaned common-dir
    path. First candidate wins per key; candidates whose rev-parse fails →
    warning git_repo_failed (skip). WorkDir of the kept entry: if the
    common dir is <X>/.git, use X; else (detached/odd layouts) keep the
    candidate dir.
  4. Deterministic output: sort Repos by Name then WorkDir.
  5. git binary absence is issue 12's Runner concern: Discover surfaces
    it as a single git_binary_missing warning and returns zero repos
    (detected on first Run error of type ErrGitNotFound).
  6. Walk must be allocation-sane on big trees: use os.ReadDir, check
    ctx.Err() per directory.

Acceptance Criteria

  • Fixture tree: root with 2 normal repos at depth 1–2, one repo below
    max_depth (excluded), one under node_modules (excluded), one
    symlinked dir (not followed), one plain dir (not a repo) → exactly the
    2 expected repos.
  • Worktree fixture: main repo + git worktree add sibling → ONE Repo,
    Name = main repo dir name (helper builds real worktrees with the
    system git).
  • Explicit repos entry pointing at a worktree of an already-discovered
    main repo → still one Repo (dedup across discovery+explicit).
  • Missing root and missing explicit repo → git_root_missing warnings,
    others unaffected.
  • Fake runner returning error for one candidate → that candidate
    skipped with git_repo_failed, others kept.
  • Determinism: shuffled directory creation order still yields identical
    sorted output.

Validation

go test -race -cover ./internal/source/gitsrc/ in PR (helper-built
fixtures; requires system git on the test machine — CI has it).

Dependencies

01, 03.

Non-goals

Commit extraction (12). Submodules: because the walk never descends past a
detected repo boundary, submodules inside a discovered repo are NOT
enumerated (their commits belong to their own repos; users who want them add
the submodule path to repos explicitly). A submodule checkout sitting
directly under a root (unusual) is treated as an ordinary repo candidate.
Bare repos: not supported in v1 (no worktree = no daily-work relevance);
rev-parse-based dedup keeps them from duplicating anything.

Design References

  • docs/DESIGN.md §7.2 (Discovery), §13
  • docs/decisions/ADR-003-read-only-sources-and-stateless-runs.md

Source of truth: docs/issues/11-git-repo-discovery.md (PR #1, branch docs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies: docs/ISSUE_PLAN.md (this is issue 11 of 33).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions