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
98 lines
3.6 KiB
Go
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
|
|
}
|