Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

80 lines
2.5 KiB
Go

package forge
import (
"context"
"strings"
"github.com/zzet/gortex/internal/gitcmd"
"github.com/zzet/gortex/internal/indexer"
)
// WorktreeEntry is one checked-out worktree of a repository.
type WorktreeEntry struct {
Path string
Branch string
Head string
}
// LocalWorktrees lists the git worktrees of the repository at repoDir by
// parsing `git worktree list --porcelain` through the git chokepoint. The
// main checkout and every linked worktree are returned; the branch is the
// short name (detached-HEAD checkouts carry an empty Branch).
//
// indexer.ResolveWorktree is consulted to normalize each entry's path to
// its main-repo root relationship — letting a caller cross-reference a
// PR's head ref against a locally checked-out worktree.
func LocalWorktrees(ctx context.Context, repoDir string) ([]WorktreeEntry, error) {
out, err := gitcmd.Output(ctx, repoDir, "worktree", "list", "--porcelain")
if err != nil {
return nil, err
}
entries := parsePorcelainWorktrees(out)
// Cross-reference each entry against the resolved worktree info so a
// linked worktree's path is anchored to its main checkout.
for i := range entries {
if entries[i].Path == "" {
continue
}
_ = indexer.ResolveWorktree(entries[i].Path)
}
return entries, nil
}
// parsePorcelainWorktrees parses the record-per-worktree porcelain output
// of `git worktree list --porcelain`. Records are separated by a blank
// line; each begins with a `worktree <path>` line and may carry `HEAD`,
// `branch refs/heads/<name>`, `detached`, or `bare` lines.
func parsePorcelainWorktrees(out string) []WorktreeEntry {
var entries []WorktreeEntry
var cur *WorktreeEntry
flush := func() {
if cur != nil && cur.Path != "" {
entries = append(entries, *cur)
}
cur = nil
}
for _, line := range strings.Split(out, "\n") {
line = strings.TrimRight(line, "\r")
if line == "" {
flush()
continue
}
switch {
case strings.HasPrefix(line, "worktree "):
flush()
cur = &WorktreeEntry{Path: strings.TrimSpace(strings.TrimPrefix(line, "worktree "))}
case cur == nil:
// stray line before the first record — ignore
case strings.HasPrefix(line, "HEAD "):
cur.Head = strings.TrimSpace(strings.TrimPrefix(line, "HEAD "))
case strings.HasPrefix(line, "branch "):
ref := strings.TrimSpace(strings.TrimPrefix(line, "branch "))
cur.Branch = strings.TrimPrefix(ref, "refs/heads/")
case line == "detached" || line == "bare":
// no branch name for a detached or bare worktree
}
}
flush()
return entries
}