Skip to content

Commit bb87278

Browse files
committed
refactor(git): centralize branch creation logic across TaskInput and git interaction
1 parent 840f7c9 commit bb87278

3 files changed

Lines changed: 135 additions & 58 deletions

File tree

apps/code/src/renderer/features/git-interaction/hooks/useGitInteraction.ts

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ import type {
1111
GitMenuActionId,
1212
} from "@features/git-interaction/types";
1313
import {
14-
sanitizeBranchName,
15-
validateBranchName,
16-
} from "@features/git-interaction/utils/branchNameValidation";
17-
import { invalidateGitBranchQueries } from "@features/git-interaction/utils/gitCacheKeys";
14+
createBranch,
15+
getBranchNameInputState,
16+
} from "@features/git-interaction/utils/branchCreation";
1817
import { updateGitCacheFromSnapshot } from "@features/git-interaction/utils/updateGitCache";
1918
import { trpc, trpcClient } from "@renderer/trpc";
2019
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
@@ -480,30 +479,25 @@ export function useGitInteraction(
480479
const runBranch = async () => {
481480
if (!repoPath) return;
482481

483-
const branchName = store.branchName.trim();
484-
if (!branchName) {
485-
modal.setBranchError("Branch name is required.");
486-
return;
487-
}
488-
489-
const validationError = validateBranchName(branchName);
490-
if (validationError) {
491-
modal.setBranchError(validationError);
492-
return;
493-
}
494-
495482
modal.setIsSubmitting(true);
496483
modal.setBranchError(null);
497484

498485
try {
499-
await trpcClient.git.createBranch.mutate({
500-
directoryPath: repoPath,
501-
branchName,
486+
const result = await createBranch({
487+
repoPath,
488+
rawBranchName: store.branchName,
502489
});
490+
if (!result.success) {
491+
if (result.reason === "request") {
492+
log.error("Failed to create branch", result.rawError ?? result.error);
493+
trackGitAction(taskId, "branch-here", false);
494+
}
503495

504-
trackGitAction(taskId, "branch-here", true);
496+
modal.setBranchError(result.error);
497+
return;
498+
}
505499

506-
invalidateGitBranchQueries(repoPath);
500+
trackGitAction(taskId, "branch-here", true);
507501
await queryClient.invalidateQueries(trpc.workspace.getAll.pathFilter());
508502

509503
modal.closeBranch();
@@ -547,9 +541,9 @@ export function useGitInteraction(
547541
setPrTitle: modal.setPrTitle,
548542
setPrBody: modal.setPrBody,
549543
setBranchName: (value: string) => {
550-
const sanitized = sanitizeBranchName(value);
544+
const { sanitized, error } = getBranchNameInputState(value);
551545
modal.setBranchName(sanitized);
552-
modal.setBranchError(validateBranchName(sanitized));
546+
modal.setBranchError(error);
553547
},
554548
runCommit,
555549
runPush,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {
2+
sanitizeBranchName,
3+
validateBranchName,
4+
} from "@features/git-interaction/utils/branchNameValidation";
5+
import { invalidateGitBranchQueries } from "@features/git-interaction/utils/gitCacheKeys";
6+
import { trpcClient } from "@renderer/trpc";
7+
8+
export interface BranchNameInputState {
9+
sanitized: string;
10+
error: string | null;
11+
}
12+
13+
export type CreateBranchResult =
14+
| {
15+
success: true;
16+
branchName: string;
17+
}
18+
| {
19+
success: false;
20+
error: string;
21+
reason: "missing-repo" | "validation" | "request";
22+
rawError?: unknown;
23+
};
24+
25+
interface CreateBranchInput {
26+
repoPath?: string;
27+
rawBranchName: string;
28+
}
29+
30+
function getCreateBranchError(error: unknown): string {
31+
return error instanceof Error ? error.message : "Failed to create branch.";
32+
}
33+
34+
export function getBranchNameInputState(value: string): BranchNameInputState {
35+
const sanitized = sanitizeBranchName(value);
36+
return {
37+
sanitized,
38+
error: validateBranchName(sanitized),
39+
};
40+
}
41+
42+
export async function createBranch({
43+
repoPath,
44+
rawBranchName,
45+
}: CreateBranchInput): Promise<CreateBranchResult> {
46+
if (!repoPath) {
47+
return {
48+
success: false,
49+
error: "Select a repository folder first.",
50+
reason: "missing-repo",
51+
};
52+
}
53+
54+
const branchName = rawBranchName.trim();
55+
if (!branchName) {
56+
return {
57+
success: false,
58+
error: "Branch name is required.",
59+
reason: "validation",
60+
};
61+
}
62+
63+
const validationError = validateBranchName(branchName);
64+
if (validationError) {
65+
return {
66+
success: false,
67+
error: validationError,
68+
reason: "validation",
69+
};
70+
}
71+
72+
try {
73+
await trpcClient.git.createBranch.mutate({
74+
directoryPath: repoPath,
75+
branchName,
76+
});
77+
78+
invalidateGitBranchQueries(repoPath);
79+
80+
return {
81+
success: true,
82+
branchName,
83+
};
84+
} catch (error) {
85+
return {
86+
success: false,
87+
error: getCreateBranchError(error),
88+
reason: "request",
89+
rawError: error,
90+
};
91+
}
92+
}

apps/code/src/renderer/features/task-detail/components/TaskInput.tsx

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import { GitBranchDialog } from "@features/git-interaction/components/GitInterac
77
import { useGitQueries } from "@features/git-interaction/hooks/useGitQueries";
88
import { useGitInteractionStore } from "@features/git-interaction/state/gitInteractionStore";
99
import {
10-
sanitizeBranchName,
11-
validateBranchName,
12-
} from "@features/git-interaction/utils/branchNameValidation";
13-
import { invalidateGitBranchQueries } from "@features/git-interaction/utils/gitCacheKeys";
10+
createBranch,
11+
getBranchNameInputState,
12+
} from "@features/git-interaction/utils/branchCreation";
1413
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
1514
import { ModeIndicatorInput } from "@features/message-editor/components/ModeIndicatorInput";
1615
import { DropZoneOverlay } from "@features/sessions/components/DropZoneOverlay";
@@ -29,7 +28,7 @@ import {
2928
import { Flex } from "@radix-ui/themes";
3029
import { useTRPC } from "@renderer/trpc/client";
3130
import { useNavigationStore } from "@stores/navigationStore";
32-
import { useMutation, useQuery } from "@tanstack/react-query";
31+
import { useQuery } from "@tanstack/react-query";
3332
import { useCallback, useEffect, useRef, useState } from "react";
3433
import { useHotkeys } from "react-hotkeys-hook";
3534
import { usePreviewSession } from "../hooks/usePreviewSession";
@@ -64,6 +63,7 @@ export function TaskInput() {
6463

6564
const [editorIsEmpty, setEditorIsEmpty] = useState(true);
6665
const [isDraggingFile, setIsDraggingFile] = useState(false);
66+
const [isCreatingBranch, setIsCreatingBranch] = useState(false);
6767
const [selectedBranch, setSelectedBranch] = useState<string | null>(null);
6868
const [selectedEnvironment, setSelectedEnvironmentRaw] = useState<
6969
string | null
@@ -108,43 +108,34 @@ export function TaskInput() {
108108
actions: gitActions,
109109
} = useGitInteractionStore();
110110

111-
const createBranchMutation = useMutation(
112-
trpcReact.git.createBranch.mutationOptions({
113-
onSuccess: (_data, { branchName }) => {
114-
if (selectedDirectory) invalidateGitBranchQueries(selectedDirectory);
115-
setSelectedBranch(branchName);
116-
gitActions.closeBranch();
117-
},
118-
onError: (error) => {
119-
const message =
120-
error instanceof Error ? error.message : "Failed to create branch.";
121-
gitActions.setBranchError(message);
122-
},
123-
}),
124-
);
125-
126111
const handleNewBranchNameChange = useCallback(
127112
(value: string) => {
128-
const sanitized = sanitizeBranchName(value);
113+
const { sanitized, error } = getBranchNameInputState(value);
129114
gitActions.setBranchName(sanitized);
130-
gitActions.setBranchError(validateBranchName(sanitized));
115+
gitActions.setBranchError(error);
131116
},
132117
[gitActions],
133118
);
134119

135-
const handleCreateBranch = useCallback(() => {
136-
const trimmed = newBranchName.trim();
137-
if (!trimmed || !selectedDirectory) return;
138-
const validationError = validateBranchName(trimmed);
139-
if (validationError) {
140-
gitActions.setBranchError(validationError);
141-
return;
120+
const handleCreateBranch = useCallback(async () => {
121+
setIsCreatingBranch(true);
122+
123+
try {
124+
const result = await createBranch({
125+
repoPath: selectedDirectory || undefined,
126+
rawBranchName: newBranchName,
127+
});
128+
if (!result.success) {
129+
gitActions.setBranchError(result.error);
130+
return;
131+
}
132+
133+
setSelectedBranch(result.branchName);
134+
gitActions.closeBranch();
135+
} finally {
136+
setIsCreatingBranch(false);
142137
}
143-
createBranchMutation.mutate({
144-
directoryPath: selectedDirectory,
145-
branchName: trimmed,
146-
});
147-
}, [newBranchName, selectedDirectory, gitActions, createBranchMutation]);
138+
}, [selectedDirectory, newBranchName, gitActions]);
148139

149140
// Preview session provides adapter-specific config options
150141
const {
@@ -459,7 +450,7 @@ export function TaskInput() {
459450
branchName={newBranchName}
460451
onBranchNameChange={handleNewBranchNameChange}
461452
onConfirm={handleCreateBranch}
462-
isSubmitting={createBranchMutation.isPending}
453+
isSubmitting={isCreatingBranch}
463454
error={branchError}
464455
/>
465456
</div>

0 commit comments

Comments
 (0)