authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-28 21:08:25-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-28 21:56:22-08:00
log29825f2f8a7cc2bb47fd32160046d47b5cf44830
tree715e8a86572dac8cba5403884bb16c523ed47b3f
parentefcee4b86354fba7457a62d8beafb4011ec3ae52

Filter detached HEAD pseudo-refs from branch listings

- ignore branch lines starting with `(` when parsing `git branch` output - add integration test to verify detached HEAD does not appear as a branch

2 files changed, 14 insertions(+), 1 deletions(-)

apps/server/src/git/Layers/GitCore.test.ts+12
......@@ -266,6 +266,18 @@ it.layer(TestLayer)("git integration", (it) => {
266266 }),
267267 );
268268
269 it.effect("does not include detached HEAD pseudo-refs as branches", () =>
270 Effect.gen(function* () {
271 const tmp = yield* makeTmpDir();
272 yield* initRepoWithCommit(tmp);
273 yield* git(tmp, ["checkout", "--detach", "HEAD"]);
274
275 const result = yield* listGitBranches({ cwd: tmp });
276 expect(result.branches.some((branch) => branch.name.startsWith("("))).toBe(false);
277 expect(result.branches.some((branch) => branch.current)).toBe(false);
278 }),
279 );
280
269281 it.effect("keeps current branch first and sorts the remaining branches by recency", () =>
270282 Effect.gen(function* () {
271283 const tmp = yield* makeTmpDir();
apps/server/src/git/Layers/GitCore.ts+2-1
......@@ -82,7 +82,8 @@ function parseBranchLine(line: string): { name: string; current: boolean } | nul
8282
8383 const name = trimmed.replace(/^[*+]\s+/, "");
8484 // Exclude symbolic refs like: "origin/HEAD -> origin/main".
85 if (name.includes(" -> ")) return null;
85 // Exclude detached HEAD pseudo-refs like: "(HEAD detached at origin/main)".
86 if (name.includes(" -> ") || name.startsWith("(")) return null;
8687
8788 return {
8889 name,