Skip to content

Commit f18417e

Browse files
committed
Implement branch picker that allows you to create or pick the branch that is used during task creation
Supports both worktree and direct/branch mode
1 parent 38af55b commit f18417e

File tree

11 files changed

+421
-20
lines changed

11 files changed

+421
-20
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)