History

Add git integration: branch picker + worktrees

Allows users to right-click "+ New thread" to create threads from specific git branches, with optional worktree creation for isolated work. Includes branch sorting (current/default first), graceful handling of non-git repos, and ability to initialize git repos. - New git IPC contract (listBranches, createWorktree, removeWorktree, init) - Branch context menu with worktree sub-option in Sidebar - Thread model extended with branch/worktreePath fields - Session cwd uses worktree path when available - Branch sorting: current > main/master > alphabetical

Julius Marminge committed 9 months ago

Commit d64f78a1 parent 9023d135 files · +79 −0Browse files

Unified · Split

5 files
  1. apps/web/src/persistenceSchema.ts +6 −0
  2. apps/web/src/types.ts +2 −0
  3. packages/contracts/src/git.ts +56 −0
  4. packages/contracts/src/index.ts +1 −0
  5. packages/contracts/src/ipc.ts +14 −0
apps/web/src/persistenceSchema.ts+6−0
@@ -34,6 +34,8 @@ const persistedThreadSchema = z.object({ · 33 unmodified lines
3434 model: z.string().min(1),
3535 messages: z.array(persistedMessageSchema),
3636 createdAt: z.string().min(1),
37+ branch: z.string().min(1).nullable().optional(),
38+ worktreePath: z.string().min(1).nullable().optional(),
3739 });
3840
3941 const persistedStateBodySchema = z.object({
@@ -112,6 +114,8 @@ function hydrateThread( · 72 unmodified lines
112114 events: [],
113115 error: null,
114116 createdAt: thread.createdAt,
117+ branch: thread.branch ?? null,
118+ worktreePath: thread.worktreePath ?? null,
115119 };
116120 }
117121
@@ -170,6 +174,8 @@ export function toPersistedState( · 52 unmodified lines
170174 model: thread.model,
171175 messages: thread.messages,
172176 createdAt: thread.createdAt,
177+ branch: thread.branch,
178+ worktreePath: thread.worktreePath,
173179 })),
174180 activeThreadId: state.activeThreadId,
175181 runtimeMode: state.runtimeMode,
apps/web/src/types.ts+2−0
@@ -35,4 +35,6 @@ export interface Thread { · 34 unmodified lines
3535 latestTurnStartedAt?: string | undefined;
3636 latestTurnCompletedAt?: string | undefined;
3737 latestTurnDurationMs?: number | undefined;
38+ branch: string | null;
39+ worktreePath: string | null;
3840 }
packages/contracts/src/git.ts+56−0
@@ -0,0 +1,56 @@
1+import { z } from "zod";
2+
3+// ── Input schemas ──
4+
5+export const gitListBranchesInputSchema = z.object({
6+ cwd: z.string().min(1),
7+});
8+
9+export const gitCreateWorktreeInputSchema = z.object({
10+ cwd: z.string().min(1),
11+ branch: z.string().min(1),
12+ path: z.string().min(1).optional(),
13+});
14+
15+export const gitRemoveWorktreeInputSchema = z.object({
16+ cwd: z.string().min(1),
17+ path: z.string().min(1),
18+});
19+
20+export const gitInitInputSchema = z.object({
21+ cwd: z.string().min(1),
22+});
23+
24+// ── Output schemas ──
25+
26+export const gitBranchSchema = z.object({
27+ name: z.string().min(1),
28+ current: z.boolean(),
29+});
30+
31+export const gitWorktreeSchema = z.object({
32+ path: z.string().min(1),
33+ branch: z.string().min(1),
34+});
35+
36+// ── Types ──
37+
38+export type GitListBranchesInput = z.infer<typeof gitListBranchesInputSchema>;
39+export type GitBranch = z.infer<typeof gitBranchSchema>;
40+export type GitCreateWorktreeInput = z.infer<
41+ typeof gitCreateWorktreeInputSchema
42+>;
43+export type GitWorktree = z.infer<typeof gitWorktreeSchema>;
44+export type GitRemoveWorktreeInput = z.infer<
45+ typeof gitRemoveWorktreeInputSchema
46+>;
47+export type GitInitInput = z.infer<typeof gitInitInputSchema>;
48+
49+export interface GitListBranchesResult {
50+ branches: GitBranch[];
51+ isRepo: boolean;
52+}
53+
54+export interface GitCreateWorktreeResult {
55+ worktree: GitWorktree;
56+}
packages/contracts/src/index.ts+1−0
@@ -6,3 +6,4 @@ export * from "./provider"; · 5 unmodified lines
66 export * from "./model";
77 export * from "./ws";
88 export * from "./project";
9+export * from "./git";
packages/contracts/src/ipc.ts+14−0
@@ -1,4 +1,12 @@
11 import type { AgentConfig, AgentExit, OutputChunk } from "./agent";
2+import type {
3+ GitCreateWorktreeInput,
4+ GitCreateWorktreeResult,
5+ GitInitInput,
6+ GitListBranchesInput,
7+ GitListBranchesResult,
8+ GitRemoveWorktreeInput,
9+} from "./git";
210 import type {
311 ProviderEvent,
412 ProviderInterruptTurnInput,
@@ -62,4 +70,10 @@ export interface NativeApi { · 57 unmodified lines
6270 shell: {
6371 openInEditor: (cwd: string, editor: EditorId) => Promise<void>;
6472 };
73+ git: {
74+ listBranches: (input: GitListBranchesInput) => Promise<GitListBranchesResult>;
75+ createWorktree: (input: GitCreateWorktreeInput) => Promise<GitCreateWorktreeResult>;
76+ removeWorktree: (input: GitRemoveWorktreeInput) => Promise<void>;
77+ init: (input: GitInitInput) => Promise<void>;
78+ };
6579 }