Worktrees und geteilte Indexe
Arbeitest du in mehreren verlinkten Git-Worktrees eines Repositorys, ist der größte Teil ihres Codes identisch. greppy indexiert diesen gemeinsamen Code einmal, in einer unveränderlichen Base, und gibt jedem Worktree ein privates Delta mit genau den Pfaden, die sich unterscheiden.
greppy index . # der erste verlinkte Worktree baut die Base, alle weiteren indexieren nur ihr Delta
greppy index status --json # Phase, Fortschritt und Bereitschaft, ohne zu blockieren
greppy index --agent-worktree # die Base des eingebauten Agenten vorab bauen oder prüfen
Ein Beispiel mit serde
Das Beispiel nutzt eine Kopie von serde 1.0.229 als Haupt-Checkout, dazu einen verlinkten Worktree pro Aufgabe:
git worktree add ../serde-feature -b feature
cd ../serde-feature && greppy index .
Der erste verlinkte Worktree baut die Base. greppy index sagt, welche Base es gebunden hat und ob es sie gebaut (created) oder wiederverwendet (reused) hat.
greppy: index started for ~/src/serde-feature (phase=preparing_base, pid=43356); progress: `greppy index status --json`
greppy: index — writing_graph: 41/219 files; phase ETA about 60s; pid 43356
greppy: index — writing_graph: 134/219 files; phase ETA about 20s; pid 43356
…
greppy: index — base_graph_ready: steps total unknown; phase ETA unavailable until total is known; pid 43356
greppy index: linked worktree uses shared Base dc1b99a0c8ed at 7fc3b4c30c94f73a96ebd1553f2b090d928fc3a8 (created); only the Git/dirty Delta will be indexed
greppy: index — finalizing_graph: 1/1 steps; phase ETA complete; pid 43356
indexed Delta generation 1: 0 changed/deleted paths, 0 private nodes (project: serde)
Jeder weitere Worktree derselben Version bindet die fertige Base und indexiert nur sein Delta.
greppy: index started for ~/src/serde-fix (phase=preparing_base, pid=55352); progress: `greppy index status --json`
greppy index: linked worktree uses shared Base dc1b99a0c8ed at 7fc3b4c30c94f73a96ebd1553f2b090d928fc3a8 (reused); only the Git/dirty Delta will be indexed
indexed Delta generation 1: 0 changed/deleted paths, 0 private nodes (project: serde)
Ändert ein Worktree eine Datei, landet nur sie im Delta. Hier bekommt Buf in serde_core/src/format.rs zwei neue Methoden, len und remaining:
greppy: index started for ~/src/serde-feature (phase=preparing_base, pid=52216); progress: `greppy index status --json`
greppy index: linked worktree uses shared Base dc1b99a0c8ed at 7fc3b4c30c94f73a96ebd1553f2b090d928fc3a8 (reused); only the Git/dirty Delta will be indexed
indexed Delta generation 2: 1 changed/deleted paths, 8 private nodes (project: serde)
Abfragen sehen die kombinierte Sicht aus Base und eigenem Delta. Die neue Methode findet nur der Worktree, der sie enthält:
$ cd serde-feature && greppy search-symbol remaining
serde_core/src/format.rs:20 Buf::remaining method
$ cd ../serde-fix && greppy search-symbol remaining
status: no_matches
scope: indexed definitions in the repository
message: no definition named `remaining`
…
Auf einem Apple-M5-Mac ergaben die drei Läufe am 07.10.2026 diese Werte:
| Worktree | Base | greppy index . |
Delta |
|---|---|---|---|
serde-feature, erster Lauf |
dc1b99a0c8ed gebaut |
103 s | 0 Pfade |
serde-fix |
dc1b99a0c8ed wiederverwendet |
5,4 s | 0 Pfade |
serde-feature, nach der Änderung |
dc1b99a0c8ed wiederverwendet |
10 s | 1 Pfad, 8 Knoten |
Base und Delta
Die Base enthält die unveränderten Repository-Daten genau einmal. Sie ist über ihren Inhalt identifiziert: Zur Identität gehören der Git-Tree, die Versionen von Store-Schema und Indexer sowie die Verträge der Summary- und Embedding-Modelle. Eine inkompatible Identität wählt eine neue Base, statt eine alte an Ort und Stelle zu migrieren.
Jeder Worktree und jeder Agent-Lauf bekommt ein beschreibbares privates Delta mit genau den Pfaden, die sich von der Base unterscheiden: geänderte, gelöschte, umbenannte oder neue Dateien, committet oder nicht.
- Die Bindung zwischen Worktree und Base ist pro Worktree gespeichert und bleibt gepinnt, wenn der Haupt-Checkout weiterzieht. Im Beispiel steht der Haupt-Checkout inzwischen auf einem neueren Commit,
serde-featurearbeitet weiter auf der Basedc1b99a0c8ed. - Weitere Worktrees nutzen eine geprüfte Base ohne neuen Checkout und ohne kompletten Durchlauf durchs Repository.
- greppy veröffentlicht eine Base atomar. Leser halten Leases, die eine Base vor dem Entfernen schützen, solange sie in Benutzung ist.
- Beschädigte oder unvollständige Bases kommen in Quarantäne und werden unter einer exklusiven Lease neu gebaut. Scheitert die Vorbereitung einer Base, meldet greppy das; ein privater Store ersetzt sie nicht stillschweigend.
doctor, diagnostics und index status --json melden den gewählten Modus (overlay für einen Worktree auf einer geteilten Base, single für einen privaten Store), die Identitäten von Base und Delta, die Vollständigkeit, Cache-Treffer, die Zahl geänderter Pfade und einen eventuellen Grund für einen Fallback.
status: ok
root: ~/src/serde-feature
…
store_mode: overlay
base_identity: dc1b99a0c8ed570e166b806f59b11821a79ff46b56616d78cdb5e05331cefed8
embedding_complete: true
…
generation: 2
stats: files=339 nodes=4575 edges=9430
…
dirty_overlay: total=1 staged=0 unstaged=1 untracked=0 deleted=0 renamed=0 ignored=0
Inference wird auch geteilt
Embeddings und Summaries liegen benutzerweit im Cache, Schlüssel sind das exakte Modell, der Prompt, das Task-Profil und die Eingabe. Ein verlinkter Worktree oder ein Agent nutzt die Vektoren, die schon berechnet sind; Inference kostet nur geänderter Delta-Inhalt. Die Index-Ausgabe und der Live-Status zählen lokale Wiederverwendung, Treffer im globalen Cache und echte Inference-Misses getrennt.
indexed 219 files (120 unsupported, 0 unreadable, 0 oversize, 0 file-limit, 0 time-budget); 4571 nodes extracted; generation 1 (project: serde)
embedded 2443 code spans (0 local-store reused, 2443 global-cache hits, 0 inference misses, 4573 considered, 2150 non-definition skipped, 0 missing-file, 0 invalid-span, 0 oversize, 0 failed, 0 stale pruned)
Warten auf einen anderen Builder
Eine Base baut immer nur ein Prozess. Baut ein anderer verlinkter Worktree sie gerade, wartet greppy index (im Status steht dann die Phase waiting_for_base_builder) und nutzt danach die veröffentlichte Base, statt eine zweite Kopie zu bauen.
Im Beispiel startet greppy index . in serde-w3 und baut eine neue Base; zehn Sekunden später startet derselbe Befehl in serde-w4 am selben Commit.
$ cd serde-w3 && greppy index .
…
greppy index: linked worktree uses shared Base bcfab63b0bf9 at d8bb8f787170a832eaec9eef4aa26bcd9512cecf (created); only the Git/dirty Delta will be indexed
$ cd serde-w4 && greppy index .
…
greppy index: linked worktree uses shared Base bcfab63b0bf9 at d8bb8f787170a832eaec9eef4aa26bcd9512cecf (reused); only the Git/dirty Delta will be indexed
serde-w4 hat gewartet und die Base übernommen, die serde-w3 gebaut hat.
greppy index status --json # blockiert nie, auch nicht während ein anderer Prozess baut
Während ein Build läuft, endet index status --json mit Exit 75 und meldet den aktuellen Zustand. Ein Status, dessen Fortschritt sich zu lange nicht bewegt, trägt "progress_stale": true. Die Schwelle liegt bei 120 Sekunden, beim Vorbereiten einer Base (preparing_base) bei 300 und beim Laden des Modells (loading_model) bei 600 Sekunden.
{
…
"background_state": "refreshing",
…
"status": "indexing",
…
"writer_active": true
}
Wo die Daten liegen
Standardmäßig liegt der Store in greppys Cache-Verzeichnis außerhalb deines Repositorys. Für ein anderes Volume setzt du GREPPY_STORE_DIR vor dem Indexieren auf ein absolutes Verzeichnis und nutzt denselben Wert für Status, Abfragen und spätere Edits:
GREPPY_STORE_DIR=/absolute/volume/greppy-store greppy index .
GREPPY_STORE_DIR=/absolute/volume/greppy-store greppy index status --json
Der Store liegt dann unter GREPPY_STORE_DIR/workspaces/. Die Variable wählt einen anderen Store; einen bestehenden verschiebt oder löscht sie nicht. TMPDIR steuert temporäre Dateien, nicht den Ort des Index, und XDG_CACHE_HOME wählt ihn ebenfalls nicht.
Modelle und Inference-Caches folgen GREPPY_STORE_DIR, außer GREPPY_SHARED_INFERENCE_ROOT ist gesetzt. Nutzt du mehrere isolierte Stores, zeig sie alle auf eine gemeinsame Inference-Root auf dem gewünschten Volume, dann bleiben Modell und Cache geteilt.
Vertraulichkeit
Base-Stores und Deltas enthalten Pfade, Spans, Graph-Relationen, Summaries und Embeddings aus dem Quellcode. Schütze sie wie das Repository selbst. Agent-Sandboxes lesen eine veröffentlichte Base, schreiben sie aber nicht; beschreibbarer Delta-Zustand bleibt pro Lauf isoliert.
Aufräumen
greppy cache status --json # Stores, Bases, Modelle, Größen, Locks
greppy cache clear --root . --yes # Store dieses Worktrees und die geteilten Agent-Bases des Repositorys
greppy cache gc --dry-run # den TTL/LRU-Garbage-Collector durchrechnen, ohne zu löschen
cache clear --root wählt auch geteilte Bases, die andere verlinkte Worktrees desselben Repositorys nutzen. Ohne --yes entfernt es nichts. Gesperrte Stores bleiben erhalten und werden gemeldet. Siehe Lokale Daten, Datenschutz und Deinstallation.
Umfang
Der Namensraum des Graphen gilt pro Repository: eine Root, eine Base plus private Deltas. Multi-Package-Workspaces innerhalb dieser Root (Cargo, npm, Go) sind schon ein gemeinsamer Relationsraum. Mehrere Repositorys fragst du mit --root pro Repository ab; ein föderierter Abfrageraum über mehrere Roots ist ein eigenes, späteres Vorhaben.