diff --git a/.changeset/js-git-status-detached-substring.md b/.changeset/js-git-status-detached-substring.md new file mode 100644 index 0000000000..08fa8c8ed3 --- /dev/null +++ b/.changeset/js-git-status-detached-substring.md @@ -0,0 +1,5 @@ +--- +"e2b": patch +--- + +Fix `parseGitStatus` so a branch or upstream whose name merely contains the substring `detached` (for example `main` tracking `origin/detached-work`) is no longer misreported as a detached HEAD. Detached-HEAD detection now keys off the `HEAD (detached at )` / `HEAD (no branch)` porcelain forms only, so `currentBranch` and `upstream` are preserved. This mirrors the Python SDK fix for the same defect (#1373). diff --git a/packages/js-sdk/src/sandbox/git/utils.ts b/packages/js-sdk/src/sandbox/git/utils.ts index 6df7fbc888..fc3ae59dc8 100644 --- a/packages/js-sdk/src/sandbox/git/utils.ts +++ b/packages/js-sdk/src/sandbox/git/utils.ts @@ -381,9 +381,7 @@ export function parseGitStatus(output: string): GitStatus { aheadStart === -1 ? undefined : branchInfo.slice(aheadStart + 2, -1) const normalizedBranch = normalizeBranchName(branchPart) const rawBranch = branchPart - const isDetached = - rawBranch.startsWith('HEAD (detached at ') || - rawBranch.includes('detached') + const isDetached = rawBranch.startsWith('HEAD (detached at ') if (isDetached || normalizedBranch.startsWith('HEAD')) { detached = true diff --git a/packages/js-sdk/tests/git.parseStatus.test.ts b/packages/js-sdk/tests/git.parseStatus.test.ts new file mode 100644 index 0000000000..306ef9bf4c --- /dev/null +++ b/packages/js-sdk/tests/git.parseStatus.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from 'vitest' + +import { parseGitStatus } from '../src/sandbox/git/utils' + +// Twin of the Python fix in PR #1374 (issue #1373): `parseGitStatus` used to flag +// `detached: true` for any branch/upstream name merely containing the substring +// "detached", dropping `currentBranch`/`upstream` for ordinary branches. + +test('upstream whose name contains "detached" is not a detached HEAD', () => { + // Branch 'main' tracking 'origin/detached-work': NOT a detached HEAD. + const status = parseGitStatus('## main...origin/detached-work\n') + expect(status.detached).toBe(false) + expect(status.currentBranch).toBe('main') + expect(status.upstream).toBe('origin/detached-work') +}) + +test('local branch whose name contains "detached" is not a detached HEAD', () => { + const status = parseGitStatus('## feature/detached-session-fix\n') + expect(status.detached).toBe(false) + expect(status.currentBranch).toBe('feature/detached-session-fix') +}) + +test('real detached HEAD ("## HEAD (no branch)") is still detected', () => { + const status = parseGitStatus('## HEAD (no branch)\n') + expect(status.detached).toBe(true) +}) + +test('real detached HEAD ("## HEAD (detached at )") is still detected', () => { + const status = parseGitStatus('## HEAD (detached at 1a2b3c4)\n') + expect(status.detached).toBe(true) +})