Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

90 lines
2.4 KiB
Go

// Package git discovers local repositories and aggregates git-derived metrics
// for session analytics.
package git
import (
"context"
"os"
"path/filepath"
"time"
gitrepo "go.kenn.io/kit/git/repo"
)
// DiscoverRepos resolves each cwd to its enclosing git repository toplevel and
// returns the deduplicated list. Cwds with no enclosing repo (or whose
// resolution fails) are silently dropped. Order follows first-seen order in
// the input.
//
// Resolution prefers `git rev-parse --show-toplevel`, which handles standard
// `.git` directories, linked worktrees (`.git` is a file pointing at the
// shared gitdir), and submodules. When the cwd no longer exists on disk, the
// helper falls back to walking upward from the nearest existing ancestor and
// invoking `git rev-parse` from there — that mirrors how the parser package
// recovers repo roots for archived sessions whose cwd has been deleted.
func DiscoverRepos(ctx context.Context, cwds []string) []string {
seen := map[string]struct{}{}
out := []string{}
for _, cwd := range cwds {
root := findRepoRoot(ctx, cwd)
if root == "" {
continue
}
if _, ok := seen[root]; ok {
continue
}
seen[root] = struct{}{}
out = append(out, root)
}
return out
}
// findRepoRoot returns the absolute repo toplevel for start, or "" when no
// enclosing repo can be resolved.
func findRepoRoot(ctx context.Context, start string) string {
if start == "" {
return ""
}
dir := existingAncestor(start)
if dir == "" {
return ""
}
return gitToplevel(ctx, dir)
}
// existingAncestor returns the closest ancestor of path that exists on disk
// and is a directory. If path itself is an existing directory, it is
// returned. Returns "" when no ancestor exists (only possible on torn
// filesystems or invalid roots).
func existingAncestor(path string) string {
dir := path
for {
info, err := os.Stat(dir)
if err == nil {
if info.IsDir() {
return dir
}
dir = filepath.Dir(dir)
continue
}
parent := filepath.Dir(dir)
if parent == dir {
return ""
}
dir = parent
}
}
// gitToplevel runs `git rev-parse --show-toplevel` from dir and returns the
// trimmed result, or "" if git fails or prints nothing. A 5s timeout guards
// against hung git invocations on broken repos.
func gitToplevel(ctx context.Context, dir string) string {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
root, err := gitrepo.Root(ctx, dir)
if err != nil {
return ""
}
return root
}