13 KiB
13 KiB
IMPLEMENT_GIT
Goal
Implement a VS Code-like Git sidebar workflow for the current project in bDS with:
- Left sidebar rail sync icon (bottom section, directly above Settings)
- Sidebar split into upper Open Changes and lower Version History
- File click to open diff view against repository in editor tabs with transient/persistent behavior
Initialize Gitaction when no repo exists- Periodic status polling and remote fetch polling
- Fetch / Pull / Push actions in repo header
- Commit message input +
Commitbutton (add-all + commit)
This plan is scoped to the existing Electron architecture:
- Main process business logic in
src/main/engine - IPC handlers in
src/main/ipc - Typed bridge in
src/main/preload.tsandsrc/main/shared/electronApi - Renderer state/UI in
src/renderer/storeandsrc/renderer/components
External Requirements
1) Runtime Dependencies
- Primary library:
simple-git - System requirement:
gitCLI installed and available in PATH - System requirement:
git-lfsCLI installed and available in PATH - Optional later: fallback to bundled Git binary for users without system Git
2) OS / Packaging
- Support macOS, Linux, Windows path handling and quoting.
- If bundling Git later, include signing/notarization and license notices in release pipeline.
3) Performance Requirements
- Must handle large change sets without list jump/reflow issues.
- Polling and fetch operations must be background/non-blocking and cancellable.
4) Security Requirements
- Run all Git commands in main process only.
- Validate project root path before command execution.
- Never allow arbitrary command injection via renderer inputs.
UX and Behavioral Requirements (from spec)
Sidebar Rail Button
- Add a new Git sync icon button in the left sidebar rail bottom section, directly above the Settings icon.
- Clicking icon opens/closes Git sidebar view similarly to existing views.
Sidebar Layout (Git view)
- Upper half: Open Changes list.
- Lower half: Version History list.
- Repo actions in header: Fetch, Pull, Push icons.
- Open Changes area includes:
- Commit message input field
- Commit button (does add-all + commit)
No Repository State
- If current project is not a git repo:
- show
Initialize Gitbutton. - action runs
git init, then enables Git LFS and tracks image file types (e.g.*.png,*.jpg,*.jpeg,*.gif,*.webp,*.svg,*.avif,*.heic) so binary image assets are excluded from normal Git object/version storage. - if git executable not found, show explicit install guidance message.
- if Git LFS executable not found, show explicit install guidance message and block completion of initialization.
- show
Diff Behavior
- Diff views open in tabs in the editor area.
- Single-click on a changed file opens a diff in a transient tab (reused for subsequent single-clicks), matching post transient tab behavior.
- Double-click on a changed file opens a dedicated non-transient diff tab that remains open until explicitly closed by the user.
- Dedicated diff tabs stay open even when other files are clicked in the sidebar.
- Open diff tabs are persisted in app tab state and restored on next app start, same persistence model as post tabs.
- Clicking a changed file opens diff view of working tree vs repository (HEAD/index depending file state).
- Committing changes automatically closes all open diff tabs because the compared diff baseline no longer applies.
Polling Behavior
- Poll git status regularly (VS Code-like freshness).
- Refresh should be incremental (preserve list item identity/order strategy where possible).
- Preserve scroll position for large lists; avoid jump-to-top.
Remote Awareness
- If remote exists, perform regular
git fetchpolling. - Show upstream relationship in version history section:
- current local HEAD
- upstream branch tip
- ahead/behind indicators
Proposed Architecture
Main Process (new engine)
Create src/main/engine/GitEngine.ts with focused methods:
checkAvailability(): Promise<{ gitFound: boolean; version?: string }>getRepoState(projectPath): Promise<RepoState>initializeRepo(projectPath): Promise<Result>getStatus(projectPath): Promise<GitStatusDto>getDiff(projectPath, filePath): Promise<GitDiffDto>getHistory(projectPath, limit, cursor?): Promise<HistoryDto>getRemoteState(projectPath): Promise<RemoteStateDto>fetch(projectPath): Promise<Result>pull(projectPath): Promise<Result>push(projectPath): Promise<Result>commitAll(projectPath, message): Promise<Result>
Implementation notes:
- Use
simple-gitinstance rooted at active project path. - Distinguish error classes:
- git missing
- not a repo
- auth/network/merge conflict
- detached HEAD / no upstream
- Normalize file paths to repo-relative format for renderer stability.
IPC Layer
Add handlers in src/main/ipc/handlers.ts and type contracts in shared API:
git:checkAvailabilitygit:getRepoStategit:initgit:statusgit:diffgit:historygit:remoteStategit:fetchgit:pullgit:pushgit:commitAll
Expose via src/main/preload.ts:
window.electronAPI.git.*methods.
Renderer State
Extend src/renderer/store/appStore.ts with Git slice:
activeViewunion includes'git'git: {availabilityrepoStatestatus(files + counts)historyremoteState(branch, upstream, ahead, behind, lastFetchAt)selectedDiffFilecommitMessageloading/action flagserror
}
Store actions:
setGitStatus,mergeGitStatusIncremental,setGitHistory,setGitRemoteStatesetSelectedDiffFile,setCommitMessagesetGitPollingState
Tab behavior extensions:
- Extend
TabTypewith'git-diff'. - Use existing transient tab mechanics for single-click diff open.
- Add/ensure explicit pinning path for double-click diff tabs (
isTransient: false). - Include diff tabs in persisted tab state (
getTabState/restoreTabState) so they reopen after restart. - On successful commit action, remove all open
'git-diff'tabs and clear selected diff state.
Renderer Components
- Update
src/renderer/components/ActivityBar/ActivityBar.tsxto place Git icon in the bottom rail group above Settings and wire view toggle. - Add Git section to
src/renderer/components/Sidebar/Sidebar.tsxrender switch. - Add dedicated presentational components:
src/renderer/components/GitSidebar/GitSidebar.tsxsrc/renderer/components/GitSidebar/OpenChangesList.tsxsrc/renderer/components/GitSidebar/VersionHistoryList.tsxsrc/renderer/components/GitSidebar/RepoActions.tsx
- Add diff tab rendering in editor area:
- new tab type
'git-diff' - diff viewer component
GitDiffView. - single-click handler opens/reuses transient diff tab.
- double-click handler opens persistent diff tab.
- new tab type
Polling and Update Strategy
Status Polling (fast)
- Interval: ~2s when Git view visible, ~5–10s when hidden.
- Trigger immediate refresh after commit/fetch/pull/push/init.
- Use in-flight guard to avoid concurrent status calls.
Remote Polling (slower)
- Run only when remote exists and git is available.
- Interval: ~30–60s with backoff on errors.
- Use
git fetch --pruneequivalent throughsimple-git.
Scroll/Render Stability
- Keep stable
key= repo-relative file path. - Preserve existing array reference for unchanged items when merging updates.
- Update only changed/added/removed entries in store (incremental diff merge).
- Use virtualization (
react-window) if list grows beyond threshold (e.g., >300 entries). - Preserve scrollTop by storing/restoring container position if full list replacement is unavoidable.
Repositioning Rules
- Do not auto-sort on every tick if sort key not changed.
- Insert new items predictably (status-group + path order) to minimize movement.
- Never auto-scroll on status update.
History Model (lower half)
Each history item should include:
commitHashauthordatesubjectisHeadisRemoteHead(where applicable)refs(branch/tag labels)
Remote awareness section:
- Show
localBranch -> upstreamBranch - Show
ahead N / behind M - Show last fetch timestamp and fetch errors (if any)
Error Handling UX
Git Missing
- Detect once on startup/opening Git view and before actions.
- Display clear CTA text: install Git and restart app.
Not a Repo
- Show empty state with
Initialize Gitbutton. - After successful init, auto-refresh status/history.
Action Failures
- Show concise toast + inline error in Git panel section.
- Keep previous state rendered (no hard reset).
Auth/Conflict Cases
- For pull/push conflicts/auth failures, show actionable message; do not hide current status/history.
Test-First Delivery Plan (TDD)
Follow strict red-green-refactor per project rules.
Phase 1: Contracts and engine scaffolding
- Add failing tests for
GitEngineavailability/repo detection/status parsing. - Implement minimal engine methods.
- Add IPC contract tests for new
git:*handlers.
Phase 2: No-repo + init workflow
- Add renderer tests for no-repo state and
Initialize Gitbutton. - Implement init action and git-missing messaging.
- Validate with integration-style IPC mock tests.
Phase 3: Open changes + diff
- Add tests for open changes list rendering and file selection.
- Add tests for opening
git-difftab and loading diff. - Add tests for single-click transient tab reuse and double-click persistent tab behavior.
- Implement diff component and tab behavior.
Phase 3b: Diff tab persistence and commit cleanup
- Add tests to verify
git-difftabs are persisted/restored via tab state. - Add tests to verify successful commit closes all open
git-difftabs. - Implement store and commit-flow wiring for cleanup behavior.
Phase 4: Commit + repo actions
- Add tests for commit message entry and commit action (
addAll + commit). - Add tests for fetch/pull/push button wiring and disabled/loading states.
- Implement action handlers with refresh chaining.
Phase 5: Polling and stability
- Add tests for polling intervals and in-flight guards (fake timers).
- Add tests for incremental list updates preserving scroll/identity.
- Implement merge strategy + optional virtualization threshold.
Phase 6: Remote tracking in history
- Add tests for ahead/behind and upstream marker rendering.
- Implement periodic fetch + remote state projection.
- Validate remote indicators against mocked git responses.
Phase 7: Hardening
- Add tests for error surfaces (git missing, auth fail, merge conflict).
- Verify all tests pass.
- Run full build and fix regressions.
Suggested File-Level Work Breakdown
Main process:
src/main/engine/GitEngine.ts(new)src/main/engine/index.ts(export)src/main/ipc/handlers.ts(new handlers)src/main/shared/electronApi.ts(API types)src/main/preload.ts(bridge methods)
Renderer:
src/renderer/store/appStore.ts(Git state/actions)src/renderer/components/ActivityBar/ActivityBar.tsx(Git icon entry in bottom rail, above Settings)src/renderer/components/Sidebar/Sidebar.tsx(Git view integration)src/renderer/components/GitSidebar/*(new)src/renderer/components/Editor/*or newGitDiffViewcomponent
Tests:
tests/engine/GitEngine.test.ts(new)tests/ipc/handlers.test.ts(extend)tests/renderer/components/GitSidebar.test.tsx(new)tests/renderer/store/appStore.git.test.ts(new)
Milestones and Acceptance Criteria
Milestone A: Basic Git UX
- Git sync icon appears in left sidebar rail bottom section above Settings.
- Git sidebar opens with no-repo empty state.
Initialize Gitworks and transitions to repo state.
Milestone B: Changes + Diff
- Open Changes list renders tracked/untracked/modified/deleted files.
- Single-click opens/reuses transient diff tab and renders correct patch.
- Double-click opens persistent diff tab that remains until user closes it.
- Diff tabs persist across app restarts.
Milestone C: Commit and Repo Actions
- Commit message + Commit button performs add-all + commit.
- Successful commit closes all open diff tabs automatically.
- Fetch/Pull/Push actions execute with visible status feedback.
Milestone D: Polling + Remote
- Status polling updates changes without scroll jump.
- Remote fetch polling updates ahead/behind and remote markers.
- History clearly shows local/remote relation.
Milestone E: Quality Gate
- All tests pass.
- Full build passes.
- No console spam, no renderer freeze on large change sets.
Implementation Order Recommendation
- Contracts + GitEngine + IPC
- No-repo/init UX
- Open changes list + diff viewer
- Commit + fetch/pull/push actions
- Polling + incremental list merge + scroll stability
- Remote-aware history refinement and hardening
This order minimizes risk and delivers user-visible value early while preserving room for performance optimization in later iterations.