-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Git integration: branch picker + worktrees #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
d64f78a
Add git integration: branch picker + worktrees
juliusmarminge 3047974
Add branch/env picker toolbar and create branch flow
juliusmarminge 5944859
Add git checkout support and clear errors on success
juliusmarminge 69e38a2
Fix typecheck errors for branch/worktree fields
juliusmarminge 8225352
Add branch/worktreePath to persistence test expectation
juliusmarminge e9cfe37
Add light mode support for git branch picker and default branch label
juliusmarminge f47dc3c
Extract git functions and add integration tests
juliusmarminge 7e20148
resource management
juliusmarminge 2a610f9
kewl
juliusmarminge c5cbe15
Fix "Open in editor" to respect worktree path
juliusmarminge 6ce773f
Fix branch query cwd for worktree threads and error banner light mode
juliusmarminge 6474490
Add test for listGitBranches from worktree cwd
juliusmarminge d1ecacd
Polish git worktree helpers after rebase
juliusmarminge c13e9f8
Restore git toolbar via websocket backend
juliusmarminge 79ac59f
eh
juliusmarminge 9fc5ad2
plans
juliusmarminge d6437a7
Move toolbar below chat input, fix electron preload bundling, and upd…
juliusmarminge 74f6ce8
Fix CI typecheck: point contracts types to source and remove dead code
juliusmarminge 4ab095f
Fix CI typecheck: build contracts before typechecking dependents
juliusmarminge e2052b8
Fix dev:desktop task graph to match main's env var patterns
juliusmarminge ceceba2
fix
juliusmarminge ca356d0
Fix server build: add zod to noExternal to prevent tsdown error
juliusmarminge 01c3e82
Suppress tsdown inlineOnly warning for server build
juliusmarminge 7e5cd01
Polish turbo task graph and dev scripts
juliusmarminge 6e3726e
Merge remote-tracking branch 'origin/main' into juliusmarminge/git-in…
juliusmarminge 198cc58
Fix envMode reset bug and use shell-free git spawn
juliusmarminge 807e3d1
Add react-query, refactor BranchToolbar async state, and fix stale deps
juliusmarminge 8324949
Enrich branches with worktree paths, fix toolbar display, and improve…
juliusmarminge 5b7af7b
Fix session not restarting when switching to a worktree branch
juliusmarminge d6ecf15
Fix worktree→local branch switch causing 'already used by worktree' e…
juliusmarminge cf66f53
Fix flash of 'Initialize git' when switching branch/worktree
juliusmarminge 9442019
Filter out stale worktree paths from branch listing
juliusmarminge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Branch/Environment Picker in ChatView Input | ||
|
|
||
| ## Summary | ||
|
|
||
| Add a secondary toolbar below the ChatView input area (similar to Codex UI) that lets users select the target branch and environment mode (Local vs New worktree) before sending their first message. | ||
|
|
||
| ## UX | ||
|
|
||
| - A toolbar appears **below** the input form (always visible when it's a git repo) | ||
| - Two controls: | ||
| 1. **Environment mode** (left side): toggles between "Local" and "New worktree" — **locked after first message** (no longer clickable, just shows current mode as label) | ||
| 2. **Branch picker** (right side): dropdown showing local branches — **always changeable**, even after messages are sent | ||
| - If not a git repo, the toolbar is hidden entirely (thread uses project cwd as-is) | ||
|
|
||
| ## Changes | ||
|
|
||
| ### 0. Install `@tanstack/react-query` in `apps/renderer` | ||
|
|
||
| Add dependency + wrap app in `QueryClientProvider`. | ||
|
|
||
| ### 1. `apps/renderer/src/store.ts` — MODIFY | ||
|
|
||
| Add a new action to the reducer: | ||
|
|
||
| ```ts | ||
| | { type: "SET_THREAD_BRANCH"; threadId: string; branch: string | null; worktreePath: string | null } | ||
| ``` | ||
|
|
||
| Reducer case updates `branch` and `worktreePath` on the thread. | ||
|
|
||
| ### 2. `apps/renderer/src/components/ChatView.tsx` — MODIFY | ||
|
|
||
| **Fetch branches** via `useQuery`: | ||
|
|
||
| ```ts | ||
| const branchQuery = useQuery({ | ||
| queryKey: ["git-branches", activeProject?.cwd], | ||
| queryFn: () => api.git.listBranches({ cwd: activeProject!.cwd }), | ||
| enabled: !!activeProject, | ||
| }); | ||
| ``` | ||
|
|
||
| **Local state:** | ||
|
|
||
| - `envMode: "local" | "worktree"` — environment mode (local component state) | ||
|
|
||
| **UI:** Below the `<form>`, render a toolbar bar (hidden if `!branchQuery.data?.isRepo`): | ||
|
|
||
| - Left side: env mode button ("Local" / "New worktree") — disabled after first message (locked in) | ||
| - Right side: branch dropdown from `branchQuery.data.branches` | ||
| - Both styled like existing model picker (small text, chevron, dropdown menus) | ||
|
|
||
| **Behavior:** | ||
|
|
||
| - Branch picker is always active — changing branch dispatches `SET_THREAD_BRANCH` immediately | ||
| - Env mode is only clickable when `activeThread.messages.length === 0`. After first message, it becomes a static label showing the locked-in mode | ||
| - On first send (`onSend`): if `envMode === "worktree"` and a branch is selected, call `api.git.createWorktree` before starting the session, then dispatch `SET_THREAD_BRANCH` with the worktreePath | ||
| - `ensureSession` already uses `activeThread.worktreePath ?? activeProject.cwd` | ||
|
|
||
| ### Files to modify | ||
|
|
||
| 1. `apps/renderer/package.json` — add `@tanstack/react-query` | ||
| 2. `apps/renderer/src/main.tsx` (or App entry) — wrap in `QueryClientProvider` | ||
| 3. `apps/renderer/src/store.ts` — add `SET_THREAD_BRANCH` action | ||
| 4. `apps/renderer/src/components/ChatView.tsx` — branch/env picker UI with `useQuery` | ||
|
|
||
| ## Verification | ||
|
|
||
| 1. `turbo build` — compiles | ||
| 2. Create a new thread → branch bar appears below input with "Local" + current branch | ||
| 3. Change branch in dropdown → branch updates on thread | ||
| 4. Toggle "New worktree" → send message → worktree created, session uses worktree cwd | ||
| 5. After first message: env mode label locks to "Worktree" (not clickable), branch picker still works | ||
| 6. Non-git project → no branch bar shown |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Git Flows Integration Tests | ||
|
|
||
| ## Overview | ||
|
|
||
| Real integration tests that run actual git commands against temporary repos. No mocking. | ||
|
|
||
| ## Step 1: Extract git functions into `apps/desktop/src/git.ts` | ||
|
|
||
| The git functions (`listGitBranches`, `createGitWorktree`, `removeGitWorktree`, `createGitBranch`, `checkoutGitBranch`, `initGitRepo`) and their helper `runTerminalCommand` are currently private in `main.ts`. Extract them into a new `apps/desktop/src/git.ts` module with named exports. | ||
|
|
||
| `main.ts` will import and re-use them — no behavior change, just moving code. | ||
|
|
||
| **Files modified:** | ||
|
|
||
| - `apps/desktop/src/git.ts` — new file with all git functions exported | ||
| - `apps/desktop/src/main.ts` — import from `./git` instead of defining inline | ||
|
|
||
| ## Step 2: Create `apps/desktop/src/git.test.ts` | ||
|
|
||
| Integration tests using real temp git repos. Each test group creates a fresh temp directory with `git init`, makes commits, creates branches as needed, and cleans up after. | ||
|
|
||
| ### Setup/teardown pattern | ||
|
|
||
| ```ts | ||
| import { mkdtemp, rm, writeFile } from "node:fs/promises"; | ||
| import { tmpdir } from "node:os"; | ||
| import path from "node:path"; | ||
| import { afterEach, beforeEach, describe, expect, it } from "vitest"; | ||
| import { | ||
| listGitBranches, | ||
| createGitBranch, | ||
| checkoutGitBranch, | ||
| createGitWorktree, | ||
| removeGitWorktree, | ||
| initGitRepo, | ||
| } from "./git"; | ||
|
|
||
| // Helper: run a raw git command in a dir (for test setup, not under test) | ||
| // Helper: create an initial commit (git needs at least one commit for branches) | ||
| ``` | ||
|
|
||
| ### Test groups | ||
|
|
||
| **1. initGitRepo** | ||
|
|
||
| - Creates a valid git repo in a temp dir | ||
| - listGitBranches reports `isRepo: true` after init | ||
|
|
||
| **2. listGitBranches** | ||
|
|
||
| - Returns `isRepo: false` for non-git directory | ||
| - Returns the current branch with `current: true` | ||
| - Sorts current branch first | ||
| - Lists multiple branches after creating them | ||
| - `isDefault` is false when no remote (no origin/HEAD) | ||
|
|
||
| **3. checkoutGitBranch** | ||
|
|
||
| - Checks out an existing branch (current flag moves) | ||
| - Throws when branch doesn't exist | ||
| - Throws when checkout would overwrite uncommitted changes (dirty working tree) | ||
|
|
||
| **4. createGitBranch** | ||
|
|
||
| - Creates a new branch (appears in listGitBranches) | ||
| - Throws when branch already exists | ||
|
|
||
| **5. createGitWorktree + removeGitWorktree** | ||
|
|
||
| - Creates a worktree directory at the expected path | ||
| - Worktree has the correct branch checked out | ||
| - Throws when branch is already checked out in another worktree | ||
| - removeGitWorktree cleans up the worktree | ||
|
|
||
| **6. Full flow: local branch checkout** | ||
|
|
||
| - init → commit → create branch → checkout → verify current | ||
|
|
||
| **7. Full flow: worktree creation from selected branch** | ||
|
|
||
| - init → commit → create branch → create worktree → verify worktree dir exists and has correct branch | ||
|
|
||
| **8. Full flow: thread switching simulation** | ||
|
|
||
| - init → commit → create branch-a, branch-b → checkout a → checkout b → checkout a → verify current matches | ||
|
|
||
| **9. Full flow: checkout conflict** | ||
|
|
||
| - init → commit → create branch → modify file (unstaged) → checkout other branch → expect error | ||
|
|
||
| ## Verification | ||
|
|
||
| ```bash | ||
| # Run the git integration tests | ||
| cd apps/desktop && bun run test | ||
|
|
||
| # Or just the git test file | ||
| npx vitest run apps/desktop/src/git.test.ts | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| # Git Flows Test Plan | ||
|
|
||
| ## Overview | ||
|
|
||
| Add tests for git branch/worktree flows. Two files: | ||
|
|
||
| 1. **Extend** `apps/renderer/src/store.test.ts` — reducer tests for `SET_THREAD_BRANCH` | ||
| 2. **Create** `apps/renderer/src/git-flows.test.ts` — flow logic tests | ||
|
|
||
| All tests are pure Vitest unit tests (no React rendering). They test the reducer directly and simulate handler logic via sequential reducer dispatches + mocked API calls. | ||
|
|
||
| ## File 1: `apps/renderer/src/store.test.ts` (extend) | ||
|
|
||
| Add `describe("SET_THREAD_BRANCH reducer")` with 6 tests: | ||
|
|
||
| - Sets branch + worktreePath atomically | ||
| - Clears both to null | ||
| - Updates branch while preserving worktreePath | ||
| - Does not affect other threads (multi-thread state) | ||
| - No-op for nonexistent thread id | ||
| - Does not mutate messages, error, or session fields | ||
|
|
||
| Uses existing `makeThread`, `makeState` factories. | ||
|
|
||
| ## File 2: `apps/renderer/src/git-flows.test.ts` (new) | ||
|
|
||
| ### Factories | ||
|
|
||
| - `makeThread()`, `makeState()`, `makeSession()` — same pattern as store.test.ts | ||
| - `makeBranch()` — creates `GitBranch` objects | ||
| - `makeMessage()` — creates `ChatMessage` objects | ||
| - `makeGitApi()` — returns `{ checkout, createWorktree, createBranch, listBranches }` with `vi.fn()` mocks | ||
|
|
||
| ### Test groups (~30 tests total) | ||
|
|
||
| **1. Local branch checkout flow** (2 tests) | ||
|
|
||
| - Successful checkout → SET_THREAD_BRANCH updates branch | ||
| - Checkout failure → SET_ERROR, branch unchanged | ||
|
|
||
| **2. Thread branch conflict on send** (3 tests) | ||
|
|
||
| - Two threads maintain independent branch state after SET_ACTIVE_THREAD | ||
| - Branch state preserved through multiple thread switches + updates | ||
| - Checkout failure on thread switch sets error only on target thread | ||
|
|
||
| **3. Worktree creation on send** (5 tests) | ||
|
|
||
| - First message in worktree mode → createWorktree → SET_THREAD_BRANCH with worktreePath | ||
| - No worktree when messages already exist | ||
| - No worktree in local envMode | ||
| - No worktree when worktreePath already set | ||
| - createWorktree failure → SET_ERROR, send aborted, no messages pushed | ||
|
|
||
| **4. Env mode locking** (4 tests) | ||
|
|
||
| - envLocked=false when no messages | ||
| - envLocked=true with messages | ||
| - Transitions false→true after PUSH_USER_MESSAGE | ||
| - Remains true after SET_ERROR and UPDATE_SESSION | ||
|
|
||
| **5. Auto-fill current branch** (3 tests) | ||
|
|
||
| - Dispatches SET_THREAD_BRANCH when thread has no branch and current branch exists | ||
| - Does not overwrite existing branch | ||
| - No-op when no branch is marked current | ||
|
|
||
| **6. Default branch detection** (2 tests) | ||
|
|
||
| - isDefault flag on branch objects | ||
| - current and isDefault can be on different branches | ||
|
|
||
| **7. Branch creation + checkout** (3 tests) | ||
|
|
||
| - Successful create + checkout updates branch | ||
| - createBranch failure → error, branch unchanged | ||
| - checkout failure after successful create → error, branch unchanged | ||
|
|
||
| **8. Session CWD resolution** (3 tests) | ||
|
|
||
| - Uses worktreePath when available | ||
| - cwdOverride takes precedence over worktreePath | ||
| - Falls back to project cwd when no worktree | ||
|
|
||
| **9. Error handling patterns** (4 tests) | ||
|
|
||
| - SET_ERROR sets error on correct thread | ||
| - SET_ERROR with null clears error | ||
| - Error on one thread doesn't affect others | ||
| - Error cleared before successful branch operations | ||
|
|
||
| ## Verification | ||
|
|
||
| ```bash | ||
| # Run all renderer tests | ||
| cd apps/renderer && bun run test | ||
|
|
||
| # Run just the new test file | ||
| npx vitest run apps/renderer/src/git-flows.test.ts | ||
|
|
||
| # Run just the store tests | ||
| npx vitest run apps/renderer/src/store.test.ts | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # Git Integration: Branch Picker + Worktrees | ||
|
|
||
| ## Summary | ||
|
|
||
| Add git integration to let users start new threads from a specific branch, optionally creating a git worktree for isolated agent work. | ||
|
|
||
| ## UX Flow | ||
|
|
||
| - **Left click** "+ New thread" → immediately creates a thread (current behavior, unchanged) | ||
| - **Right click** "+ New thread" → opens a context menu with git options: | ||
| - List of local branches → clicking one creates a thread on that branch (uses project cwd) | ||
| - Each branch has a "worktree" sub-option → creates a worktree, then creates thread with worktree as cwd | ||
| - When thread has a worktree, the agent session uses the worktree path as its cwd | ||
| - If git fails (not a repo), context menu shows "Not a git repository" disabled item | ||
|
|
||
| ## Changes | ||
|
|
||
| ### 1. `packages/contracts/src/git.ts` — CREATE | ||
|
|
||
| New Zod schemas and types: | ||
|
|
||
| - `gitListBranchesInputSchema` — `{ cwd: string }` | ||
| - `gitCreateWorktreeInputSchema` — `{ cwd: string, branch: string, path?: string }` | ||
| - `gitRemoveWorktreeInputSchema` — `{ cwd: string, path: string }` | ||
| - `gitBranchSchema` — `{ name: string, current: boolean }` | ||
| - Result types for each | ||
|
|
||
| ### 2. `packages/contracts/src/ipc.ts` — MODIFY | ||
|
|
||
| - Add 3 IPC channels: `git:list-branches`, `git:create-worktree`, `git:remove-worktree` | ||
| - Add `git` namespace to `NativeApi` with `listBranches`, `createWorktree`, `removeWorktree` | ||
|
|
||
| ### 3. `packages/contracts/src/index.ts` — MODIFY | ||
|
|
||
| - Add `export * from "./git"` | ||
|
|
||
| ### 4. `apps/desktop/src/main.ts` — MODIFY | ||
|
|
||
| Add 3 IPC handlers + helper functions: | ||
|
|
||
| - `listGitBranches()` — runs `git branch --no-color`, parses output into `{ name, current }[]` | ||
| - `createGitWorktree()` — runs `git worktree add <path> <branch>`, defaults path to `../{repo}-worktrees/{branch}` | ||
| - `removeGitWorktree()` — runs `git worktree remove <path>` | ||
|
|
||
| Reuses existing `runTerminalCommand()`. | ||
|
|
||
| ### 5. `apps/desktop/src/preload.ts` — MODIFY | ||
|
|
||
| Add `git` namespace with 3 `ipcRenderer.invoke` calls. | ||
|
|
||
| ### 6. `apps/renderer/src/types.ts` — MODIFY | ||
|
|
||
| Add to `Thread`: | ||
|
|
||
| ``` | ||
| branch: string | null | ||
| worktreePath: string | null | ||
| ``` | ||
|
|
||
| ### 7. `apps/renderer/src/persistenceSchema.ts` — MODIFY | ||
|
|
||
| - Add optional `branch`/`worktreePath` to persisted thread schema (`.nullable().optional()` for backwards compat) | ||
| - Add V3 schema, update union | ||
| - Update `hydrateThread` to default new fields to `null` | ||
| - Update `toPersistedState` to serialize new fields | ||
|
|
||
| ### 8. `apps/renderer/src/store.ts` — MODIFY | ||
|
|
||
| - Update persisted state key to v3, keep v2 as legacy fallback | ||
|
|
||
| ### 9. `apps/renderer/src/components/Sidebar.tsx` — MODIFY (main UI work) | ||
|
|
||
| - Keep existing left-click `handleNewThread` unchanged (immediate thread creation) | ||
| - Add `onContextMenu` handler to "+ New thread" buttons (both global and per-project) | ||
| - On right-click: fetch branches via `api.git.listBranches`, show a custom context menu | ||
| - Context menu items: branch names, each with a nested option to create with worktree | ||
| - Clicking a branch → creates thread with `branch` set, title = branch name | ||
| - Clicking "with worktree" → calls `api.git.createWorktree` first, then creates thread with `worktreePath` | ||
| - Show branch badge on thread list items | ||
| - If not a git repo, show "Not a git repository" as disabled menu item | ||
|
|
||
| Context menu component: a positioned `<div>` with `position: fixed` anchored to the click position, dismissed on click-outside or Escape. Follows the existing dropdown pattern from ChatView's model picker. | ||
|
|
||
| ### 10. `apps/renderer/src/components/ChatView.tsx` — MODIFY | ||
|
|
||
| - Line 157: use `activeThread.worktreePath ?? activeProject.cwd` as session cwd | ||
| - Show branch/worktree badge in header bar | ||
|
|
||
| ## Implementation Order | ||
|
|
||
| 1. `packages/contracts/src/git.ts` (new schemas) | ||
| 2. `packages/contracts/src/ipc.ts` + `index.ts` (wire up channels) | ||
| 3. `apps/desktop/src/main.ts` (git command handlers) | ||
| 4. `apps/desktop/src/preload.ts` (bridge methods) | ||
| 5. `apps/renderer/src/types.ts` (Thread type update) | ||
| 6. `apps/renderer/src/persistenceSchema.ts` + `store.ts` (persistence migration) | ||
| 7. `apps/renderer/src/components/Sidebar.tsx` (branch picker UI) | ||
| 8. `apps/renderer/src/components/ChatView.tsx` (worktree cwd + badge) | ||
|
|
||
| ## Edge Cases | ||
|
|
||
| - **Not a git repo**: `git branch` fails → context menu shows "Not a git repository" disabled item | ||
| - **Branch has slashes**: `feature/foo` → worktree dir becomes `feature-foo` | ||
| - **Worktree exists**: git error surfaces to user via inline error message in context menu | ||
| - **No persistence breakage**: `.nullable().optional()` fields parse fine with old data | ||
|
|
||
| ## Verification | ||
|
|
||
| 1. `turbo build` — confirm contracts/desktop/renderer all compile | ||
| 2. Launch app, add a project pointing to a git repo | ||
| 3. Click "+ New thread" → verify branch list loads | ||
| 4. Select a branch, click Start → thread created with branch in title | ||
| 5. Enable worktree checkbox, pick branch, Start → verify worktree directory created on disk | ||
| 6. Send a message in worktree thread → verify agent runs in worktree cwd | ||
| 7. Add a non-git project → verify graceful error, can still create thread |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
bunxinstead ofnpxin the verification snippet.This keeps docs consistent with the Bun-only tooling standard.
✏️ Proposed doc fix
Based on learnings: Use Bun as the package manager. Use
bunx <BIN>when executing scripts from npm packages not installed as a project dependency.🤖 Prompt for AI Agents