From a498d1935d90258df055f5f3b5b8d2130c9ad835 Mon Sep 17 00:00:00 2001 From: gaojunran Date: Sat, 22 Aug 2026 11:44:23 +0800 Subject: [PATCH] fix(git): don't treat bare HEAD as a branch diff base git switch -c / git checkout -b from the current branch record 'branch: Created from HEAD' in the reflog, but parseBranchCreationSource only rejected 'HEAD@{...}' (detached start) and raw commit hashes. The bare HEAD passed through, so getBranchBase returned { base: 'HEAD' } and the branch scope computed diffs against HEAD itself, which is empty when the service is checked out on that branch and wrong otherwise. Treat bare HEAD like HEAD@{...}: no named source is recorded, so return null and let the UI ask the user to pick a base. --- packages/web/server/lib/git/service.js | 7 +++++-- packages/web/server/lib/git/service.test.js | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/web/server/lib/git/service.js b/packages/web/server/lib/git/service.js index d8e97b37..77d507be 100644 --- a/packages/web/server/lib/git/service.js +++ b/packages/web/server/lib/git/service.js @@ -2660,7 +2660,8 @@ const BRANCH_CREATION_SOURCE_RE = /^branch: Created from (.+)$/; * Parse a branch reflog (`git reflog show --format=%gs `) and return the * ref the branch was created from, when that source is itself a named ref. * - * Returns null when the branch was created from `HEAD@{...}` or a raw commit + * Returns null when the branch was created from `HEAD` (bare, as `git switch -c` + * / `git checkout -b` without an explicit start point record) or a raw commit * (detached start): the original branch name is not recorded anywhere in that * case, and guessing a base from commit topology would be a heuristic, not an * answer. Callers should ask the user to pick a base instead. @@ -2675,7 +2676,9 @@ export function parseBranchCreationSource(reflogText) { const match = lines[index].match(BRANCH_CREATION_SOURCE_RE); if (!match) continue; const source = match[1].trim(); - if (!source || /^HEAD@/.test(source) || /^[0-9a-f]{7,40}$/i.test(source)) { + // Bare `HEAD` (`git switch -c` from the current branch) and `HEAD@{...}` + // (detached start) both lack a named source; a raw commit hash does too. + if (!source || /^HEAD(@|$)/.test(source) || /^[0-9a-f]{7,40}$/i.test(source)) { return null; } return source; diff --git a/packages/web/server/lib/git/service.test.js b/packages/web/server/lib/git/service.test.js index eb86c2ac..8875fae0 100644 --- a/packages/web/server/lib/git/service.test.js +++ b/packages/web/server/lib/git/service.test.js @@ -1354,6 +1354,14 @@ describe('parseBranchCreationSource', () => { expect(parseBranchCreationSource(reflog)).toBeNull(); }); + it('returns null when the branch was created from the current HEAD without a named source', () => { + // `git switch -c ` / `git checkout -b ` from the current + // branch record `branch: Created from HEAD` in the reflog (git 2.x). The + // source branch name is not recorded, so no base can be derived from it. + const reflog = 'branch: Created from HEAD'; + expect(parseBranchCreationSource(reflog)).toBeNull(); + }); + it('returns null when the branch was created from a raw commit', () => { const reflog = 'branch: Created from 9a3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b'; expect(parseBranchCreationSource(reflog)).toBeNull();