git-views
Git-Ansichten in zid im VS-Code-Stil: Diff-Editor, Timeline, Source Control Graph, Multi-File-Diff, Changes mit Commit/Sync/Publish. Use when touching src/git/*, git_diff_view.zig, git_commit_view.zig
SKILL.md.claude/skills/git-views/SKILL.md
GitHubGit-Ansichten: gemeinsame Bausteine
- Repo-Verlauf zeigt der Source Control Graph, Datei-Verlauf die Timeline im Explorer; es gibt keinen eigenen History-Tab.
git_worker.frame/unframe/splitKey: Worker-Ergebnisse als<schlüssel>\n<text>, bei Commit-Anfragen\x1f<hash>im Schlüssel, damit veraltete Antworten erkannt werden. Fehler von git kommen als*_error-Tag mit stderr als Text, nie als Task-Fehler (framedResult).src/git/git_list.zig(Modulgit_list, unit-getestet):visibleRange,scrollToShow,clampScrollfür virtualisierte Listen mit fester Zeilenhöhe (Timeline, Graph).- Alle git-Aufrufe laufen mit
core.quotepath=off. - Beenden bricht laufende git-Prozesse ab (
git_worker.killRunning, in main.zig vorscheduler.deinit), sonst hält ein langsamergit logeinen Worker über die 2 s Wartezeit des Schedulers hinaus fest. Unter Windows laufen die Prozesse in einem Job-Objekt, weilbin\git.exenur ein Launcher fürmingw64\bin\git.exeist undTerminateProcessauf den Launcher den echten git weiterlaufen lässt. Unter Linux startet git mit eigener Prozessgruppe (pgid = 0),kill(-pid)trifft auch Hooks, die sonst die stderr-Pipe offen hielten. E2Epython3 scripts/e2e_git_shutdown.py: eincore.fsmonitor = sleep 30im Fixture lässtgit statushängen, zid muss sich in unter 2 s ohne zurückgelassenen Worker beenden. Ein so abgebrochener Aufruf ist kein Fehler:taskGitStatus/runGitCwdgeben beiisStopping()error.Canceledzurück, das der Scheduler nicht loggt; der Test verlangt ein Log ohneerror(-Zeilen. - Branch über
symbolic-ref --short -q HEAD(geht auch ohne ersten Commit), nur bei losgelöstem HEADrev-parse --abbrev-ref HEAD. - Fremde Repos („detected dubious ownership“): Gehört das Repo einem anderen Benutzer
(Netzlaufwerk, anderes Konto), verweigert git jeden Befehl.
taskGitStatusmeldet danngit_unsafe_repomit dem Wert, den git selbst fürsafe.directoryvorschlägt (unsafeRepoDirectory, wörtlich übernommen: auf Windows-Netzlaufwerken ist das%(prefix)///host/…, aus dem Projektpfad nicht sicher ableitbar).UI.handleUnsafeRepofragt wie VS Code „Manage Unsafe Repositories“ nach, je Wert einmal pro Sitzung (unsafe_repo), sonst käme die Rückfrage nach jeder Dateiänderung. Ja führt die Aktiontrust_repoaus (config --global --add safe.directory), danach laden Status und Branch (git_branch_wanted) neu. Bewusst nicht still per-c safe.directory: die Prüfung verhindert, dass die.git/configeines fremden Repos (core.fsmonitor) beim automatischengit statusProgramme startet. Die übrigen Tasks loggen nur eine Zeile (error.GitUnsafeRepo). E2Epython3 scripts/e2e_unsafe_repo.py:GIT_TEST_ASSUME_DIFFERENT_OWNER=1stellt den fremden Besitzer nach,GIT_CONFIG_GLOBALhält die Benutzer-Config heraus. - E2E: RPC
git_diff_stateliefert den Diff-Editor des aktiven Tabs (view: "diff"), JSON vom Main-Thread pro Frame gespiegelt (snapshotGitViews, mittimeline_stateundscm_state).
Diff-Editor im VS-Code-Stil (Tab git-diff://…)
- Vorbild ist VS Codes Diff-Editor, Standardwerte aus
src/vs/editor/common/config/diffEditor.ts: Automatic-Layout (nebeneinander ab 900 px, sonst untereinander),+/−-Markierung, unveränderte Bereiche standardmäßig sichtbar, eingeklappt mit Kontext 3 / Minimum 3. Titel wieresolveTimelineOpenDiffCommand:name (eltern) ↔ name (commit), Wurzel-Commit gegen den leeren Baum4b825dc. Kürzel wie VS Code: Alt+F5 / Shift+Alt+F5 (diff_next_change/diff_prev_change), Umschalter „Toggle Collapse Unchanged Regions“ und „Toggle Inline View“ in der Werkzeugleiste. - Aufbau:
src/git/git_diff.zig(Modulgit_diff, unit-getestet: Tab-Pfad mit 0x1f-Feldern Commit/Eltern/Repo/Pfad/alter Pfad, Hunks ausgit show -U0 -M, Ausrichtung je Layout,collapse,innerChange,columnSlice,DiffState), WorkertaskGitFileDiff(alter Inhalt<eltern>:<alter pfad>, neuer<commit>:<pfad>, fehlende Seite leer), Ansichtsrc/ui/git_diff_view.zig. Die Zeilen-Ausrichtung übernimmt git (Hunks), zid berechnet keinen Diff. - Beide Seiten nutzen den Tree-sitter-Highlighter wie die Markdown-Codeblöcke; Hälften sind
.percent(0.5), weil feste Breiten aus dem Vorframe im neuen Pane den Container aufziehen. - Binär und UTF-16 (
file_types.looksBinaryauf einer der Seiten,GitDiffView.binary): nur der Hinweis wie VS Code, keine Zeilen, kein Highlighter — sonst ginge je Zeile ungültiges UTF-8 an den Shaper. Im Multi-File-Diff eine Hinweiszeile je Abschnitt. RPCgit_diff_state.binary. - Kein gültiges UTF-8 (ASP-Dateien in Windows-1252):
taskGitFileDiffwandelt beide Seiten mitgit_diff.toUtf8als ganze Datei aus Windows-1252 um. Ein Byte ergibt ein Zeichen, die Zeilen und damit die Hunk-Nummern bleiben gleich. RPCgit_diff_state.new_headzeigt die ersten drei neuen Zeilen. - Tab-Pfade enthalten 0x1f: RPC-JSON immer über
std.json.Stringifyschreiben (ui_state.tabs). - E2E
python3 scripts/e2e_git_diff.py(öffnet den Tab peropen_filemit gebautem Pfad), Zustand übergit_diff_state.
Timeline im Explorer (VS-Code-Stil)
- Abschnitt „TIMELINE“ unter dem Dateibaum (
sidebar= Explorer +timeline_view.zig), anfangs eingeklappt wie VS Code, Auf-Zustand inuser_state(timeline_expanded). Quellen:timelinePane.ts(Zeitspalte, ausgeblendete gleiche Zeit, Meldungen, Pin/Refresh nur beim Überfahren),timelineProvider.ts(Label = erste Nachrichtenzeile, Autor als Beschreibung,previousRef= nächstälterer Commit der Datei, beim ältesten der leere Baum),hover.ts,base/common/date.ts(fromNow-Kurzformen),git.timeline.date= committed. - Logik in
src/git/git_timeline.zig(Modulgit_timeline, unit-getestet), WorkertaskGitTimeline(git log --follow --numstat;--shortstatmit--name-onlyliefert keine Zahlen,%pist in deutscher Locale leer → 24 h). Klick öffnet den Diff-Editor (openGitDiff), Rechtsklicktimeline_menu_items. Die Hunks kommen dafür ausgit diff <voriger Datei-Commit> <commit>. - Folgt dem aktiven Tab (Text, Bild, PDF, Binär, Vorschau-Quelle); Diff-Tabs lassen die Timeline stehen, weil ihr Pfad der historische Name ist. Geladen wird nur aufgeklappt; Dateiereignisse (git-status-Debounce) laden neu.
- „File History“ im Explorer-, Tab- und Editor-Kontextmenü (
file_history,file_history_entry) stellt die Timeline wie VS Codefiles.openTimelineauf diese Datei: aufgeklappt, angepinnt, ohne Tab zu öffnen (Timeline.show). Aus dem Editor-Menü läuft das überpending_file_historyinupdate(), nie im Render-Pfad:showgibt den Log frei, auf den Clay-Texte des Frames zeigen. Pin lösen folgt sofort dem aktiven Tab (resetTimelineFollow). - Tastatur nach Klick in Kopf oder Liste (
sidebar_focus = .timeline): ↑↓/PgUp/PgDn/Home/End wählen, Enter öffnet den Diff wie ein Klick, F5 lädt neu, Escape gibt den Fokus ab. - E2E
python3 scripts/e2e_timeline.py(Fixture-Repo mit festen Commit-Zeiten), RPCtimeline_state.
Source Control Graph und Multi-File-Diff (VS-Code-Stil)
- Ctrl+Shift+G schaltet die Sidebar auf Source Control (
UI.sidebar_mode), Ctrl+Shift+E zurück. Kopf „SOURCE CONTROL GRAPH“ mit Filter „Auto“ und Refresh; Commit-Zeile = Graph, Betreff, Autor, Badges der gefilterten Referenzen; Klick klappt die Dateien auf, Klick auf eine Datei öffnet den Diff-Editor, Rechtsklickgraph_menu_items, Inline-Aktion und Menü „Open Changes“ öffnen den Multi-File-Diff (git-commit://…, Titel „kurz - betreff“ wiegit.viewCommit). Am Listenende lädt die nächste Seite automatisch (scm.graph.pageOnScroll, 50 je Seite). - Tastatur: Klick in die Sidebar oder Ctrl+Shift+G setzt
UI.sidebar_focus = .scm; dann wählen ↑↓/PgUp/PgDn/Home/End Zeilen (View.moveSelection), Enter/Leertaste klappt Commits auf bzw. öffnet die Datei im Diff-Editor (activateSelected), ←/→ klappen zu/auf, F5 lädt neu. Keine Taste und kein Zeichen erreicht den Editor; Escape, Klick anderswo, Tab-Öffnen oder Pane-Wechsel geben den Fokus ab. Gleiches Modell für die Timeline (.timeline,Timeline.moveSelection). - Quellen:
scmHistory.ts(Bahnen und Zeichnen, 1:1 portiert insrc/git/git_graph.zig),scmHistoryViewPane.ts(Zeilen, Badgesscm.graph.badges = filter),historyProvider.ts(Referenzen, Filter Auto = Branch + Upstream + Basis),git.ts(--topo-order --decorate=full --shortstat --diff-merges=first-parent),diffEditorItemTemplate.ts(Multi-Diff: unveränderte Bereiche eingeklappt, Status R/D/A, Klappknopf je Datei). - Daten in
src/git/git_scm.zig(Modulgit_scm, unit-getestet: Referenzen, Filter, Log, Dateien, Zeilen, Load More), WorkertaskGitGraphLog/taskGitCommitChanges, Ansichtensrc/ui/scm_graph_view.zigundsrc/ui/git_commit_view.zig(jede Datei einGitDiffView, Inhalt lädt erst, wenn der Abschnitt sichtbar wird). - Graph zeichnen ohne Rundungen im Renderer: gerade Linien sind Rechtecke, Bögen und Kreise je ein kleines SVG mit eigenem Pfad. Das ist Absicht: Der Rasterizer füllt nach Even-Odd (mehrere Formen in einem Pfad schneiden Löcher) und der Atlas rastert höchstens vier neue Formen pro Durchgang — als eigene Formen je Radius und Quadrant werden sie wiederverwendet.
- Graph-Log ohne
--shortstat: auf einem Netzlaufwerk mit losen Objekten kostet es je Seite 35–50 s statt 2–6 s.Commit.statist einStatState(unknown/loading/failed/loaded); das Überfahren einer Zeile fordert die Zahlen dieses einen Commits an (View.requestStat, WorkertaskGitCommitStatmitgit_scm.statArgs), die Hover-Karte zeigt bis dahin „Loading changes…“. E2E ine2e_scm_graph.py(scm_state.commits[].stat). Der Rest der Wartezeit ist--topo-order: git liest dafür den ganzen Verlauf, bevor es die erste Zeile ausgibt. git_worker.FieldsParamtrennt Felder mit 0x1e, weil Schlüssel selbst 0x1f enthalten (graph<generation>\x1f<hash>);generationverwirft Ergebnisse von vor einem Refresh.- E2E
python3 scripts/e2e_scm_graph.py(Fixture mit Remote, Merge, Tag und 55 Commits Vorlauf), RPCscm_state.
Changes-Bereich mit Commit (über dem Graphen)
- Kopf „SOURCE CONTROL“ (Commit, Sync Changes bzw. Publish Branch, Refresh beim Überfahren;
VS Code hat statt Sync ein „…“-Menü mit Pull/Push/Fetch/Sync), mehrzeiliges Eingabefeld
(
EditBuffermit ZeilenfunktionenmoveUp/moveDown/moveLineHome/moveLineEnd/setCursorAtLine, Enter = neue Zeile, Ctrl+Enter = Commit, wächst bisINPUT_MAX_LINES= 6 wiescm.inputMaxLineCount, danach scrollt es zur Cursorzeile; PlatzhalterMessage (Ctrl+Enter to commit on "<branch>")), großer Knopf wiescm.showActionButton: „Commit“ solange Änderungen da sind, sauber ohne Upstream „Publish Branch“ (push -u origin <branch>), sauber und voraus oder zurück „Sync Changes M↓ N↑“ (Zähler nur wenn > 0, wieactionButton.ts; Aktionsync=pull, dannpush, VS Codegit.sync; Upstream/Vorsprung/Rückstand aus# branch.upstreamund# branch.ab). Keingit.rebaseWhenSync, kein Autofetch:behindist erst nach einem Fetch bekannt. Einzelne Push-/Pull-Knöpfe gibt es wie in VS Code nicht. Gruppen „Merge Changes“ (nur bei Konflikten), „Staged Changes“ (nur wenn nicht leer), „Changes“ (immer, untracked darin =git.untrackedChanges: mixed). Zeile: Name, Ordner gedimmt, rechts Buchstabe intheme.git_*(VS CodegitDecoration.*), gelöscht durchgestrichen. Aktionen nur beim Überfahren (Reihenfolge wiepackage.json): Datei öffnen, Stage/Unstage, Discard; Köpfe: Stage All / Unstage All / Discard All. IDssc_actmit Indexzeile * 8 + RowAction. - Das Eingabefeld ist ein
CodeEditormitfont_size = 16undline_pad = 6(ZeilenhöhelineHeight()=INPUT_LINE_HEIGHT22); mit den Editor-Vorgaben (24 + 16) schneidet das Feld die untere Hälfte der Buchstaben ab. - Daten
src/git/git_changes.zig(Modulgit_changes, unit-getestet): Gruppen aus der rohen porcelain-v2-Ausgabe, dietaskGitStatushinter 0x1c mitliefert (splitStatusPayload), Buchstabe/ Farbe/Hover-Text/Durchstreichen wieResourceinrepository.ts, Diff-Spec je Zeile (Staged = HEAD↔Indexindex_ref, Changes = Index↔Arbeitskopiegit_diff.worktree_ref, Untracked = leerer Baum↔Arbeitskopie mitsyntheticAddHunk), Auswahl, Discard-Rückfragen. Ansichtsrc/ui/scm_changes_view.zig, Aktionen laufen inUI.runScmAction. - Worker
taskGitAction(FieldsParam: Aktion, Repo, Pfade bzw. Nachricht):stage=add -A --,unstage=reset -q HEAD --,discard_tracked=checkout -q --,discard_untracked=clean -f -q --,commit=commit --quiet --file - --allow-empty-message(Nachricht über stdin,runGitCaptureStdin),commit_all= vorheradd -A(VS Code smartCommit),push=push --quietplus weitere Felder als Argumente (Publish Branch),sync=pull --quiet, bei Erfolgpush --quiet(scheitert der Pull, etwa Konflikt oder divergiert ohnepull.rebase, kommt gits Meldung und kein Push). Fehler kommen alsgit_action_errormit stderr → Toast. Ergebnis setztgit_status_wanted, main.zig lädt den Status über den Debounce; nach Commit und Sync auch Graph und Timeline. - Generate Commit Message (Sparkle rechts oben im Feld, wie VS Code Copilot / Zed): Worker-
Aktion
commit_diffliefert den gestagten Diff, sonst Arbeitskopie plus untracked Dateien;git_changes.commitPrompt(Conventional Commits, 72 Zeichen, Body, nur die Nachricht; Diff auf 24 KiB gekappt) geht blockierend überai_worker.taskChatCompletionmit eigenen Tagsai_commit_message/_error(ChatParams.reply_tag), damit die Antwort nicht im Chat landet;cleanGeneratedMessageentfernt Zäune, Anführungszeichen und Label. Ohne KI (--ai=off, Agent nichtready) nur ein Toast. Kein Streaming, kein eigener Systemprompt-Schalter. - Commit-Verhalten wie
smartCommit: leere Nachricht → Hinweis unter dem Feld; keine Staged Changes → Rückfrage „stage all and commit“; nichts geändert → Toast. Discard fragt immer (Texte und Knöpfe auscommands.ts: Discard File / Restore File / Delete File / Discard All n Files). Rückfragen laufen überactive_dialogmitscm_pending. - Fokus: Ctrl+Shift+G setzt
sidebar_focus = .commit_input; Tab wandert Feld → Changes-Liste → Graph → Feld. In der Liste ↑↓/PgUp/PgDn/Home/End, Enter öffnet den Diff bzw. klappt den Kopf, Entf = Discard mit Rückfrage, Escape gibt ab. Buchstaben gehen nur ins Feld. - E2E
python3 scripts/e2e_scm_changes.py(Fixture: geändert, gelöscht, untracked; stage, Diffs, unstage, discard mit Dialog, commit mit Rückfrage, Publish, Sync mit Vorsprung, Sync holt fremde Commits aus einem zweiten Klon über den großen Knopf nach Fetch und über den Kopf-Knopf ohne Fetch, Tastatur), Zustand inscm_state.changes. Das Skript baut nicht selbst, sondern startetzig-out/bin/ziddirekt — vorherzig build, sonst testet es den alten Stand. Der RPCopen_projectwechselt den Projektordner wie der Dialog (Explorer, Watcher, Branch, git status),open_folderlädt nur den Explorer.