Module: internal/git (gitx) · Milestone: M3 · Effort: ~4.5w
Reliably clone/sync/inspect the set of repos in a workspace, using the developer's existing git auth, with a fast parallel UX and a headline cross-repo status table.
- Shell out to the system
gitbinary (require ≥ 2.30) behind a small internalgitxpackage — not go-git.go-git/v5only as a build-tagged, read-only, offline fallback (never on the auth/network path). - Why exec(git): it inherits the SSH agent,
~/.ssh/config(IdentityFile, Host aliases, ProxyJump/Include),known_hosts(with host-key verification), and OS credential helpers (osxkeychain, libsecret, GCM) for free. That "your existinggit pushsetup just works" is the entire value proposition. - Hardened exec env on every call:
GIT_TERMINAL_PROMPT=0,GCM_INTERACTIVE=never(never block on a hidden prompt — fail fast per-repo),LC_ALL=C(stable parseable output),--no-optional-lockson reads.exec.LookPath("git")once at startup with an actionable error if missing/old. - Status via
git status --porcelain=v2 --branch -z, parsed directly (~80 lines, no dependency): dirty/staged/unstaged/untracked + ahead/behind + upstream-gone in one cheap call. - HTTPS token injection via a generated
GIT_ASKPASSshim (tiny0600temp script that echoes the token from the secrets provider), never embedding tokens in the URL or.git/config. - Shorthand expansion:
github:/gitlab:/bitbucket: org/repo+ genericrepo: <url>+ pluggable self-hostedhosts:map; transport (ssh|https) chosen by workspace config. Supersets devdock's!Repomodel so existing users map over directly. - Parallelism:
golang.org/x/sync/errgroupwithSetLimit(--jobs, default min(8, 2*GOMAXPROCS)), per-repocontextfor cancel/timeout. - UX:
bubbletea+bubbles+lipglossrow-per-repo table when attached to a TTY; plain prefixed line logging ([repo] cloning…) +--jsonwhen not (CI/WSL2), detected viagolang.org/x/term. Dual-mode is mandatory. - Submodules/shallow opt-in per repo:
submodules: true→--recurse-submodules; preferfilter: blob:none(partial clone) over--depthfor large repos; document the shallow+submodule pitfall; auto-unshallow on a missing-ref error.
ws clone [all|<name>…] · ws sync (fetch + --ff-only pull, parallel) · ws status (the headline table; --check exits non-zero if any repo dirty) · ws git <args> -- [all|<name>…] (arbitrary git across many repos). Idempotent: clone skips existing dirs and validates the remote matches the expected URL.
- go-git does parse
~/.ssh/configbut only honorsHostname/Port— it ignoresIdentityFile/User/ProxyJump/Include, so deploy-key + bastion setups silently break; it also fails when noknown_hostsexists. (This is why we exec system git.) - go-git CVEs (2026): pin ≥ v5.19.x (not just v5.17.1) for the full advisory set; v6 is still alpha. The "shallow leaves
.gitfull-size" claim is a config default (Tags: AllTags) — setTags: NoTags+SingleBranch. go-git has no true partial clone (--filter), only shallow. --porcelain=v2stability is documented for v1 only — treat v2 as de-facto stable, tolerate added headers, pin to observed fields. No explicit upstream-gone field — detect viabranch.upstreampresent +branch.ababsent. Min git for v2/--branchis ~2.13; the 2.30 floor is conservative (note Ubuntu 20.04 ships 2.25.1, below it).GIT_ASKPASSshim could leak the token to disk — write0600in anos.MkdirTempdir and remove withdefer.- Progress parsing is locale/version-sensitive —
LC_ALL=C; treat clone/fetch progress as best-effort cosmetic (phase + spinner), not parsed percentages.
-
ws clone allclones every repo in parallel using the user's existing SSH key +~/.ssh/configHost alias, with no credential prompt. - A repo behind a
ProxyJumpbastion clones successfully (proving system-git inheritance). -
ws statusshows dirty/ahead/behind/branch/upstream-gone for every repo in one table;--checkexits non-zero when any is dirty. - HTTPS token from the secrets provider authenticates a clone via the
GIT_ASKPASSshim, with the token never written to.git/configor visible inps. - In a non-TTY/CI context, output is plain prefixed lines (or
--json), not TUI escape codes. - A missing-credential repo fails fast with a clear per-repo error instead of hanging the parallel batch.
Consumes internal/secrets (token for the GIT_ASKPASS shim) + internal/config (the repo set). Feeds internal/workspace (which repos to manage) and the onboarding flow (feature #1).