feat(git-graph): VS Code-style git graph with commit actions in History modal (#1431)

* feat(types): add parents to GitLogEntry and new commit action types

* feat(git): add parent hashes and --all flag to getLog

* fix(git): move record separator to start of log format string

* feat(git): add checkoutCommit server function and route

* feat(git): add cherryPick server function and route

* feat(git): add revertCommit server function and route

* feat(git): add resetToCommit server function and route

* fix(tests): make git service tests branch-name portable, add error path tests

* feat(client): add checkoutCommit, cherryPick, revertCommit, resetToCommit API wrappers

* feat(git-graph): add lane assignment algorithm with tests

* feat(git-graph): add GitGraphSegment per-row SVG renderer

* feat(i18n): add locale strings for git graph action buttons

* fix(git-graph): handle lane convergence, fix SVG path coords, add connector tests

* feat(git-graph): add ref badges and action buttons to HistoryCommitRow

* fix(git-graph): add loading guards to reset actions, use theme tokens for ref badges

* fix(git-graph): conditional hooks, stale graph log, conflict handling, i18n

* fix(types): replace toBeDefined with toBeTruthy, fix toast API usage

* fix(lint): remove unused variables

* fix(git-graph): fix SVG height causing 150px row spacing

* fix(git-graph): smooth bezier curves, fill row height, round line caps

* fix(git-graph): non-scaling-stroke fixes bezier white spaces, sort curves on top

* fix(git-graph): remove viewBox scaling, match SVG height to actual row height

* fix(git-graph): ResizeObserver tracks actual row height, eliminates SVG height mismatch

* feat(git-graph): replace SVG with Canvas for graph rendering

* fix(git-graph): isolate canvas from flex layout to prevent replaced-element height leak

* feat(git-graph): align action buttons, add confirmation popups for all actions

* fix(git-graph): address code review findings CR-001 through CR-005

- CR-001: VS Code getGitLog now forwards 'all' option and parses %P parents
- CR-002: VS Code bridge/gitService implement checkoutCommit, cherryPick,
  revertCommit, resetToCommit with conflict detection and hard-reset guard
- CR-003: server-side commit hash validated with /^[0-9a-fA-F]{7,40}$/
  in both routes.js and service.js; 12 new rejection tests added
- CR-004: cherry-pick/revert conflict path now refreshes fetchStatus/
  fetchBranches/fetchLog; conflict toast uses i18n keys in all 7 locales
- CR-005: corrected O(n) comment to O(n x lanes)

* fix(i18n): add zh-TW locale and common.language.traditionalChinese key to all locales

upstream/main added zh-TW.ts after branch diverged; CI type-check fails
when PR is merged because zh-TW.ts was missing all gitView.history.actions.*
keys and loadMore/loadingMore. Also adds common.language.traditionalChinese
to en.ts and all 6 non-English files to match upstream en.ts.

* fix: harden git history actions

* feat: split git history graph view

* chore: remove git graph planning docs

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Erman HAVUÇ
2026-05-27 00:13:25 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent cc3d1bd63c
commit 52ffe9daef
26 changed files with 2373 additions and 111 deletions
+31
View File
@@ -504,11 +504,39 @@ export const dict: Record<I18nKey, string> = {
"gitView.header.identityTooltip": "Identidad de Git",
"gitView.header.noIdentity": "Sin identidad",
"gitView.header.noProfiles": "No hay perfiles disponibles para aplicar.",
"gitView.header.repositoryViews": "Vistas del repositorio",
"gitView.header.removeRemoteAria": "Eliminar remoto",
"gitView.header.removeRemoteTitle": "Eliminar remoto",
"gitView.header.upstreamSynced": "sincronizado",
"gitView.header.upstreamTooltip": "Comparado con {target}.",
"gitView.header.upstreamTooltipTracking": "Comparado con {target}. Los indicadores principales de sincronización aún reflejan {tracking}.",
"gitView.history.actions.cancelButton": "Cancelar",
"gitView.history.actions.checkoutConfirm": "¿Cambiar a este commit como HEAD separado?",
"gitView.history.actions.cherryPickConfirm": "¿Aplicar cherry-pick de este commit sobre la rama actual?",
"gitView.history.actions.conflictToastDescription": "Conflictos en: {files}. Resuélvelos manualmente y haz commit, o aborta con git cherry-pick/revert --abort.",
"gitView.history.actions.conflictToastTitle": "Conflicto",
"gitView.history.actions.confirmButton": "Confirmar",
"gitView.history.actions.mergeConfirm": "¿Fusionar este commit en la rama actual?",
"gitView.history.actions.rebaseConfirm": "¿Rebasear la rama actual sobre este commit?",
"gitView.history.actions.resetHardConfirm": "Reset hard — HEAD se mueve y todos los cambios sin commit se descartan permanentemente.",
"gitView.history.actions.resetMixedConfirm": "Reset mixed — HEAD se mueve y los cambios quedan sin preparar.",
"gitView.history.actions.resetSoftConfirm": "Reset soft — HEAD se mueve y los cambios preparados se conservan.",
"gitView.history.actions.revertConfirm": "¿Preparar una reversión de este commit?",
"gitView.history.actions.checkout": "Cambiar a commit",
"gitView.history.actions.cherryPick": "Cherry-pick",
"gitView.history.actions.createBranch": "Crear rama aquí",
"gitView.history.actions.createBranchConfirm": "Crear",
"gitView.history.actions.createBranchPlaceholder": "Nombre de rama",
"gitView.history.actions.detachedHead": "Checkout realizado (HEAD separado)",
"gitView.history.actions.merge": "Fusionar en la actual",
"gitView.history.actions.rebase": "Rebasear sobre este",
"gitView.history.actions.reset": "Reset...",
"gitView.history.actions.resetHard": "Hard — descartar todos los cambios",
"gitView.history.actions.resetHardConfirmButton": "Descartar cambios",
"gitView.history.actions.resetMixed": "Mixed — quitar del stage",
"gitView.history.actions.resetSoft": "Soft — conservar staged",
"gitView.history.actions.revert": "Revertir",
"gitView.history.binary": "Binario",
"gitView.history.binaryNoDiff": "Archivo binario — no hay diff disponible",
"gitView.history.commitsPlaceholder": "Buscar commits...",
@@ -518,6 +546,8 @@ export const dict: Record<I18nKey, string> = {
"gitView.history.largeDiffTitle": "Diff grande ({count} líneas cambiadas)",
"gitView.history.loadingDiff": "Cargando diff...",
"gitView.history.loadingFiles": "Cargando archivos...",
"gitView.history.loadMore": "Cargar más",
"gitView.history.loadingMore": "Cargando...",
"gitView.history.logSize100": "100 commits",
"gitView.history.logSize25": "25 commits",
"gitView.history.logSize50": "50 commits",
@@ -526,6 +556,7 @@ export const dict: Record<I18nKey, string> = {
"gitView.history.renamedNoDiff": "Archivo renombrado — diff no soportado",
"gitView.history.renderDiffAnyway": "Renderizar igualmente",
"gitView.history.title": "Historial",
"gitView.graph.title": "Grafo",
"gitView.integrate.checking": "Verificando…",
"gitView.integrate.cherryPickAbortedToast": "Cherry-pick abortado",
"gitView.integrate.cherryPickConflictDescription": "Resuelve los conflictos de cherry-pick para continuar.",