Skip to content

Commit 7947393

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 0481eab commit 7947393

File tree

11 files changed

+422
-23
lines changed

11 files changed

+422
-23
lines changed

apps/array/src/main/preload.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
277277
}> => ipcRenderer.invoke("get-diff-stats", repoPath),
278278
getCurrentBranch: (repoPath: string): Promise<string | undefined> =>
279279
ipcRenderer.invoke("get-current-branch", repoPath),
280+
getDefaultBranch: (repoPath: string): Promise<string> =>
281+
ipcRenderer.invoke("get-default-branch", repoPath),
282+
getAllBranches: (repoPath: string): Promise<string[]> =>
283+
ipcRenderer.invoke("get-all-branches", repoPath),
284+
createBranch: (repoPath: string, branchName: string): Promise<void> =>
285+
ipcRenderer.invoke("create-branch", repoPath, branchName),
280286
discardFileChanges: (
281287
repoPath: string,
282288
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
@@ -83,7 +83,8 @@ export class WorkspaceService {
8383
async createWorkspace(
8484
options: CreateWorkspaceOptions,
8585
): Promise<WorkspaceInfo> {
86-
const { taskId, mainRepoPath, folderId, folderPath, mode } = options;
86+
const { taskId, mainRepoPath, folderId, folderPath, mode, branch } =
87+
options;
8788
log.info(
8889
`Creating workspace for task ${taskId} in ${mainRepoPath} (mode: ${mode})`,
8990
);
@@ -116,6 +117,25 @@ export class WorkspaceService {
116117

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

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

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)