Skip to content

Commit 6caf4e5

Browse files
committed
feat(code): unified PR creation workflow
1 parent 7db3177 commit 6caf4e5

File tree

8 files changed

+68
-49
lines changed

8 files changed

+68
-49
lines changed

apps/code/src/main/services/git/service.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import { DiscardFileChangesSaga } from "@posthog/git/sagas/discard";
2626
import { PullSaga } from "@posthog/git/sagas/pull";
2727
import { PushSaga } from "@posthog/git/sagas/push";
2828
import { parseGitHubUrl } from "@posthog/git/utils";
29-
import { isCodeBranch } from "@shared/constants";
3029
import { inject, injectable } from "inversify";
3130
import { MAIN_TOKENS } from "../../di/tokens";
3231
import { logger } from "../../utils/logger";
@@ -229,8 +228,7 @@ export class GitService extends TypedEventEmitter<GitServiceEvents> {
229228
}
230229

231230
public async getAllBranches(directoryPath: string): Promise<string[]> {
232-
const branches = await getAllBranches(directoryPath);
233-
return branches.filter((branch) => !isCodeBranch(branch));
231+
return getAllBranches(directoryPath);
234232
}
235233

236234
public async createBranch(

apps/code/src/renderer/features/git-interaction/components/BranchSelector.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Combobox } from "@components/ui/combobox/Combobox";
22
import { useGitInteractionStore } from "@features/git-interaction/state/gitInteractionStore";
3-
import { getSuggestedBranchName } from "@features/git-interaction/utils/deriveBranchName";
3+
import { getSuggestedBranchName } from "@features/git-interaction/utils/getSuggestedBranchName";
44
import { invalidateGitBranchQueries } from "@features/git-interaction/utils/gitCacheKeys";
55
import { GitBranch, Plus } from "@phosphor-icons/react";
66
import { Flex, Spinner, Tooltip } from "@radix-ui/themes";
@@ -144,7 +144,9 @@ export function BranchSelector({
144144
onClick={() => {
145145
setOpen(false);
146146
actions.openBranch(
147-
taskId ? getSuggestedBranchName(taskId) : undefined,
147+
taskId
148+
? getSuggestedBranchName(taskId, repoPath ?? undefined)
149+
: undefined,
148150
);
149151
}}
150152
>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import {
1414
createBranch,
1515
getBranchNameInputState,
1616
} from "@features/git-interaction/utils/branchCreation";
17-
import { getSuggestedBranchName } from "@features/git-interaction/utils/deriveBranchName";
1817
import { updateGitCacheFromSnapshot } from "@features/git-interaction/utils/updateGitCache";
1918
import { trpc, trpcClient } from "@renderer/trpc";
2019
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
2120
import { useQueryClient } from "@tanstack/react-query";
2221
import { track } from "@utils/analytics";
2322
import { logger } from "@utils/logger";
2423
import { useMemo } from "react";
24+
import { getSuggestedBranchName } from "../utils/getSuggestedBranchName";
2525

2626
const log = logger.scope("git-interaction");
2727

@@ -167,7 +167,8 @@ export function useGitInteraction(
167167
publish: () => modal.openPush("publish"),
168168
"view-pr": () => viewPr(),
169169
"create-pr": () => openCreatePr(),
170-
"branch-here": () => modal.openBranch(getSuggestedBranchName(taskId)),
170+
"branch-here": () =>
171+
modal.openBranch(getSuggestedBranchName(taskId, repoPath)),
171172
};
172173
actionMap[id]();
173174
};

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ export function useGitQueries(repoPath?: string) {
106106
),
107107
);
108108

109+
useQuery(
110+
trpc.git.getAllBranches.queryOptions(
111+
{ directoryPath: repoPath as string },
112+
{
113+
enabled: repoEnabled,
114+
...GIT_QUERY_DEFAULTS,
115+
staleTime: 60_000,
116+
},
117+
),
118+
);
119+
109120
const hasChanges = changedFiles.length > 0;
110121
const aheadOfRemote = syncStatus?.aheadOfRemote ?? 0;
111122
const behind = syncStatus?.behind ?? 0;

apps/code/src/renderer/features/git-interaction/utils/deriveBranchName.test.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,48 @@ import { deriveBranchName } from "./deriveBranchName";
44
describe("deriveBranchName", () => {
55
it("converts a simple title to a branch name", () => {
66
expect(deriveBranchName("Fix authentication login bug", "abc123")).toBe(
7-
"posthog/fix-authentication-login-bug",
7+
"posthog-code/fix-authentication-login-bug",
88
);
99
});
1010

1111
it("handles special characters", () => {
1212
expect(deriveBranchName("PostHog issue #1234", "abc123")).toBe(
13-
"posthog/posthog-issue-1234",
13+
"posthog-code/posthog-issue-1234",
1414
);
1515
});
1616

1717
it("collapses consecutive dashes", () => {
1818
expect(deriveBranchName("Fix the bug", "abc123")).toBe(
19-
"posthog/fix-the-bug",
19+
"posthog-code/fix-the-bug",
2020
);
2121
});
2222

2323
it("strips leading and trailing dashes", () => {
24-
expect(deriveBranchName(" Fix bug ", "abc123")).toBe("posthog/fix-bug");
24+
expect(deriveBranchName(" Fix bug ", "abc123")).toBe(
25+
"posthog-code/fix-bug",
26+
);
2527
});
2628

2729
it("truncates long titles", () => {
2830
const longTitle =
2931
"This is a very long task title that should be truncated to a reasonable length for git";
3032
const result = deriveBranchName(longTitle, "abc123");
31-
expect(result.length).toBeLessThanOrEqual(68); // 60 slug + "posthog/" prefix
32-
expect(result.startsWith("posthog/")).toBe(true);
33+
expect(result.length).toBeLessThanOrEqual(73); // 60 slug + "posthog-code/" prefix
34+
expect(result.startsWith("posthog-code/")).toBe(true);
3335
expect(result.endsWith("-")).toBe(false);
3436
});
3537

3638
it("falls back to task ID when title is empty", () => {
37-
expect(deriveBranchName("", "abc123")).toBe("posthog/task-abc123");
39+
expect(deriveBranchName("", "abc123")).toBe("posthog-code/task-abc123");
3840
});
3941

4042
it("falls back to task ID when title is only whitespace", () => {
41-
expect(deriveBranchName(" ", "abc123")).toBe("posthog/task-abc123");
43+
expect(deriveBranchName(" ", "abc123")).toBe("posthog-code/task-abc123");
4244
});
4345

4446
it("falls back to task ID when title is only special characters", () => {
45-
expect(deriveBranchName("!@#$%", "abc123")).toBe("posthog/task-abc123");
47+
expect(deriveBranchName("!@#$%", "abc123")).toBe(
48+
"posthog-code/task-abc123",
49+
);
4650
});
4751
});
Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { Task } from "@shared/types";
2-
import { queryClient } from "@utils/queryClient";
1+
import { BRANCH_PREFIX } from "@shared/constants";
32

43
export function deriveBranchName(title: string, fallbackId: string): string {
54
const slug = title
@@ -11,21 +10,6 @@ export function deriveBranchName(title: string, fallbackId: string): string {
1110
.slice(0, 60)
1211
.replace(/-$/, "");
1312

14-
if (!slug) return `posthog/task-${fallbackId}`;
15-
return `posthog/${slug}`;
16-
}
17-
18-
export function getSuggestedBranchName(taskId: string): string {
19-
const queries = queryClient.getQueriesData<Task[]>({
20-
queryKey: ["tasks", "list"],
21-
});
22-
let task: Task | undefined;
23-
for (const [, tasks] of queries) {
24-
task = tasks?.find((t) => t.id === taskId);
25-
if (task) break;
26-
}
27-
const fallbackId = task?.task_number
28-
? String(task.task_number)
29-
: (task?.slug ?? taskId);
30-
return deriveBranchName(task?.title ?? "", fallbackId);
13+
if (!slug) return `${BRANCH_PREFIX}task-${fallbackId}`;
14+
return `${BRANCH_PREFIX}${slug}`;
3115
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { deriveBranchName } from "@features/git-interaction/utils/deriveBranchName";
2+
import { trpc } from "@renderer/trpc";
3+
import type { Task } from "@shared/types";
4+
import { queryClient } from "@utils/queryClient";
5+
6+
export function getSuggestedBranchName(
7+
taskId: string,
8+
repoPath?: string,
9+
): string {
10+
const queries = queryClient.getQueriesData<Task[]>({
11+
queryKey: ["tasks", "list"],
12+
});
13+
let task: Task | undefined;
14+
for (const [, tasks] of queries) {
15+
task = tasks?.find((t) => t.id === taskId);
16+
if (task) break;
17+
}
18+
const fallbackId = task?.task_number
19+
? String(task.task_number)
20+
: (task?.slug ?? taskId);
21+
const base = deriveBranchName(task?.title ?? "", fallbackId);
22+
23+
if (!repoPath) return base;
24+
25+
const cached = queryClient.getQueryData<string[]>(
26+
trpc.git.getAllBranches.queryKey({ directoryPath: repoPath }),
27+
);
28+
if (!cached?.includes(base)) return base;
29+
30+
let n = 2;
31+
while (cached.includes(`${base}-${n}`)) n++;
32+
return `${base}-${n}`;
33+
}

apps/code/src/shared/constants.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,4 @@
1-
/**
2-
* Branch naming conventions.
3-
* - Reading: Accept all prefixes for backwards compatibility
4-
* - Writing: Always use BRANCH_PREFIX (posthog-code/)
5-
*/
61
export const BRANCH_PREFIX = "posthog-code/";
7-
export const LEGACY_BRANCH_PREFIXES = ["twig/", "array/", "posthog/"];
8-
9-
export function isCodeBranch(branchName: string): boolean {
10-
return (
11-
branchName.startsWith(BRANCH_PREFIX) ||
12-
LEGACY_BRANCH_PREFIXES.some((p) => branchName.startsWith(p))
13-
);
14-
}
15-
162
export const DATA_DIR = ".posthog-code";
173
export const WORKTREES_DIR = ".posthog-code/worktrees";
184
export const LEGACY_DATA_DIRS = [

0 commit comments

Comments
 (0)