Skip to content

Commit 4611a75

Browse files
authored
feat: Implement branch picker (#222)
1 parent 9478212 commit 4611a75

File tree

12 files changed

+406
-19
lines changed

12 files changed

+406
-19
lines changed

apps/array/src/main/preload.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
282282
}> => ipcRenderer.invoke("get-diff-stats", repoPath),
283283
getCurrentBranch: (repoPath: string): Promise<string | undefined> =>
284284
ipcRenderer.invoke("get-current-branch", repoPath),
285+
getDefaultBranch: (repoPath: string): Promise<string> =>
286+
ipcRenderer.invoke("get-default-branch", repoPath),
287+
getAllBranches: (repoPath: string): Promise<string[]> =>
288+
ipcRenderer.invoke("get-all-branches", repoPath),
289+
createBranch: (repoPath: string, branchName: string): Promise<void> =>
290+
ipcRenderer.invoke("create-branch", repoPath, branchName),
285291
discardFileChanges: (
286292
repoPath: string,
287293
filePath: string,

apps/array/src/main/services/git.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,36 @@ export const getDefaultBranch = async (
153153
}
154154
};
155155

156+
export const getAllBranches = async (
157+
directoryPath: string,
158+
): Promise<string[]> => {
159+
try {
160+
const { stdout } = await execAsync(
161+
'git branch --list --format="%(refname:short)"',
162+
{
163+
cwd: directoryPath,
164+
},
165+
);
166+
return stdout
167+
.trim()
168+
.split("\n")
169+
.filter(Boolean)
170+
.map((branch) => branch.trim())
171+
.filter((branch) => !branch.startsWith("array/"));
172+
} catch {
173+
return [];
174+
}
175+
};
176+
177+
export const createBranch = async (
178+
directoryPath: string,
179+
branchName: string,
180+
): Promise<void> => {
181+
await execAsync(`git checkout -b "${branchName}"`, {
182+
cwd: directoryPath,
183+
});
184+
};
185+
156186
const getChangedFiles = async (directoryPath: string): Promise<Set<string>> => {
157187
const changedFiles = new Set<string>();
158188

@@ -785,6 +815,37 @@ export function registerGitIpc(
785815
},
786816
);
787817

818+
ipcMain.handle(
819+
"get-default-branch",
820+
async (
821+
_event: IpcMainInvokeEvent,
822+
directoryPath: string,
823+
): Promise<string> => {
824+
return getDefaultBranch(directoryPath);
825+
},
826+
);
827+
828+
ipcMain.handle(
829+
"get-all-branches",
830+
async (
831+
_event: IpcMainInvokeEvent,
832+
directoryPath: string,
833+
): Promise<string[]> => {
834+
return getAllBranches(directoryPath);
835+
},
836+
);
837+
838+
ipcMain.handle(
839+
"create-branch",
840+
async (
841+
_event: IpcMainInvokeEvent,
842+
directoryPath: string,
843+
branchName: string,
844+
): Promise<void> => {
845+
return createBranch(directoryPath, branchName);
846+
},
847+
);
848+
788849
ipcMain.handle(
789850
"discard-file-changes",
790851
async (

apps/array/src/main/services/workspace/workspaceService.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ export class WorkspaceService {
8484
async createWorkspace(
8585
options: CreateWorkspaceOptions,
8686
): Promise<WorkspaceInfo> {
87-
const { taskId, mainRepoPath, folderId, folderPath, mode } = options;
87+
const { taskId, mainRepoPath, folderId, folderPath, mode, branch } =
88+
options;
8889
log.info(
8990
`Creating workspace for task ${taskId} in ${mainRepoPath} (mode: ${mode})`,
9091
);
@@ -117,6 +118,25 @@ export class WorkspaceService {
117118

118119
// Root mode: skip worktree creation entirely
119120
if (mode === "root") {
121+
// try to create the branch, if it already exists just switch to it
122+
if (branch) {
123+
try {
124+
log.info(`Creating/switching to branch ${branch} for task ${taskId}`);
125+
try {
126+
await execAsync(`git checkout -b "${branch}"`, { cwd: folderPath });
127+
log.info(`Created and switched to new branch ${branch}`);
128+
} catch (_error) {
129+
await execAsync(`git checkout "${branch}"`, { cwd: folderPath });
130+
log.info(`Switched to existing branch ${branch}`);
131+
}
132+
} catch (error) {
133+
log.error(`Failed to create/switch to branch ${branch}:`, error);
134+
throw new Error(
135+
`Failed to create/switch to branch ${branch}: ${String(error)}`,
136+
);
137+
}
138+
}
139+
120140
// Save task association without worktree
121141
const associations = getTaskAssociations();
122142
const existingIndex = associations.findIndex((a) => a.taskId === taskId);
@@ -219,7 +239,9 @@ export class WorkspaceService {
219239
let worktree: WorktreeInfo;
220240

221241
try {
222-
worktree = await worktreeManager.createWorktree();
242+
worktree = await worktreeManager.createWorktree({
243+
baseBranch: branch ?? undefined,
244+
});
223245
log.info(
224246
`Created worktree: ${worktree.worktreeName} at ${worktree.worktreePath}`,
225247
);

apps/array/src/renderer/features/folder-picker/components/FolderPicker.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,15 @@ export function FolderPicker({
6161
onClick={handleOpenFilePicker}
6262
>
6363
<Flex justify="between" align="center" gap="2" width="100%">
64-
<Flex
65-
align="center"
66-
gap="2"
67-
width="250px"
68-
style={{ minWidth: 0, flex: 1 }}
69-
>
64+
<Flex align="center" gap="2">
7065
<FolderIcon size={16} weight="regular" style={{ flexShrink: 0 }} />
7166
<Text
7267
size={size}
7368
style={{
7469
overflow: "hidden",
7570
textOverflow: "ellipsis",
7671
whiteSpace: "nowrap",
72+
maxWidth: "120px",
7773
}}
7874
truncate
7975
>
@@ -90,13 +86,8 @@ export function FolderPicker({
9086
<DropdownMenu.Root>
9187
<DropdownMenu.Trigger>
9288
<Button color="gray" variant="outline" size={size}>
93-
<Flex justify="between" align="center" gap="2" width="100%">
94-
<Flex
95-
align="center"
96-
gap="2"
97-
width="250px"
98-
style={{ minWidth: 0, flex: 1 }}
99-
>
89+
<Flex justify="between" align="center" gap="2">
90+
<Flex align="center" gap="2" style={{ minWidth: 0 }}>
10091
<FolderIcon
10192
size={16}
10293
weight="regular"

0 commit comments

Comments
 (0)