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

98 lines
3.6 KiB
Go

package indexer
import (
"context"
"encoding/json"
"time"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/gitcmd"
"github.com/zzet/gortex/internal/graph"
)
// persistRepoIndexState records the per-repo freshness provenance at the
// end of a (re)index. diskTarget is the durable store when indexing
// streams to disk; nil falls back to idx.graph. Backends without durable
// state (the in-memory graph) do not implement RepoIndexStateWriter, so
// the write is skipped — exactly like the file-mtime ledger.
func (idx *Indexer) persistRepoIndexState(diskTarget graph.Store, rootAbs, workspaceFP string, nodes, edges int) {
target := graph.Store(idx.graph)
if diskTarget != nil {
target = diskTarget
}
w, ok := target.(graph.RepoIndexStateWriter)
if !ok {
return
}
sha, dirty := repoHeadAndDirty(rootAbs)
vers, _ := json.Marshal(extractorVersionsSnapshot())
st := graph.RepoIndexState{
RepoPrefix: idx.repoPrefix,
IndexedSHA: sha,
Dirty: dirty,
IndexedAt: time.Now().Unix(),
WorkspaceFP: workspaceFP,
NodeCount: nodes,
EdgeCount: edges,
ExtractorVersions: string(vers),
}
if err := w.SetRepoIndexState(st); err != nil {
idx.logger.Warn("persist repo index state failed",
zap.String("repo", idx.repoPrefix), zap.Error(err))
}
}
// reconcileRepoIndexState re-stamps the per-repo freshness row at the
// current HEAD after the git-watcher catches the index up to a new
// commit. The full (re)index is otherwise the only writer of this row,
// so without this the row keeps the SHA from the last full index and
// `gortex repos` reports the repo stale even though the in-memory graph
// already reflects HEAD. The Merkle baseline (WorkspaceFP) from that
// last full index is preserved — the incremental reconcile diffs against
// it but never rebuilds it. No-op on backends without durable index
// state (the in-memory graph is not a RepoIndexStateWriter).
func (idx *Indexer) reconcileRepoIndexState(rootAbs string) {
prevFP := ""
if r, ok := graph.Store(idx.graph).(graph.RepoIndexStateReader); ok {
if prev, found, _ := r.GetRepoIndexState(idx.repoPrefix); found {
prevFP = prev.WorkspaceFP
}
}
nodes, edges := idx.repoNodeEdgeCount()
idx.persistRepoIndexState(nil, rootAbs, prevFP, nodes, edges)
}
// repoHeadAndDirty returns the working tree's current commit SHA and
// whether it has uncommitted changes. Best-effort: a non-git directory or
// any git error yields ("", false) — freshness provenance never blocks
// indexing. Git shell-outs route through the shared concurrency limiter.
func repoHeadAndDirty(rootAbs string) (sha string, dirty bool) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
sha, err := gitcmd.Output(ctx, rootAbs, "rev-parse", "HEAD")
if err != nil {
return "", false
}
status, err := gitcmd.Output(ctx, rootAbs, "status", "--porcelain")
if err != nil {
return sha, false
}
return sha, status != ""
}
// repoHead returns the working tree's current commit SHA, or "" for a non-git
// directory or any git error. A lighter probe than repoHeadAndDirty when the
// caller does not yet need the dirty bit — it skips the git status shell-out,
// which is the slow half on a large repo. The dirty bit is fetched separately
// (via repoHeadAndDirty) only when the cheap sha check leaves the decision open.
func repoHead(rootAbs string) string {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
sha, err := gitcmd.Output(ctx, rootAbs, "rev-parse", "HEAD")
if err != nil {
return ""
}
return sha
}