Skip to content

Commit 838bd0f

Browse files
authored
fix(git): surface checkout errors for existing branches (#2122)
1 parent 3028a00 commit 838bd0f

File tree

5 files changed

+83
-6
lines changed

5 files changed

+83
-6
lines changed

extensions/git/scripts/bash/create-new-feature.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,11 @@ if [ "$DRY_RUN" != true ]; then
366366
if [ "$ALLOW_EXISTING" = true ]; then
367367
if [ "$current_branch" = "$BRANCH_NAME" ]; then
368368
:
369-
elif ! git checkout "$BRANCH_NAME" 2>/dev/null; then
369+
elif ! switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1); then
370370
>&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again."
371+
if [ -n "$switch_branch_error" ]; then
372+
>&2 printf '%s\n' "$switch_branch_error"
373+
fi
371374
exit 1
372375
fi
373376
elif [ "$USE_TIMESTAMP" = true ]; then

extensions/git/scripts/powershell/create-new-feature.ps1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,13 @@ if (-not $DryRun) {
327327
if ($currentBranch -eq $branchName) {
328328
# Already on the target branch
329329
} else {
330-
git checkout -q $branchName 2>$null | Out-Null
330+
$switchBranchError = git checkout -q $branchName 2>&1 | Out-String
331331
if ($LASTEXITCODE -ne 0) {
332-
Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again."
332+
if ($switchBranchError) {
333+
Write-Error "Error: Branch '$branchName' exists but could not be checked out.`n$($switchBranchError.Trim())"
334+
} else {
335+
Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again."
336+
}
333337
exit 1
334338
}
335339
}

scripts/bash/create-new-feature.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,11 @@ if [ "$DRY_RUN" != true ]; then
337337
if [ "$current_branch" = "$BRANCH_NAME" ]; then
338338
:
339339
# Otherwise switch to the existing branch instead of failing.
340-
elif ! git checkout "$BRANCH_NAME" 2>/dev/null; then
340+
elif ! switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1); then
341341
>&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again."
342+
if [ -n "$switch_branch_error" ]; then
343+
>&2 printf '%s\n' "$switch_branch_error"
344+
fi
342345
exit 1
343346
fi
344347
elif [ "$USE_TIMESTAMP" = true ]; then

scripts/powershell/create-new-feature.ps1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,13 @@ if (-not $DryRun) {
315315
# Already on the target branch — nothing to do
316316
} else {
317317
# Otherwise switch to the existing branch instead of failing.
318-
git checkout -q $branchName 2>$null | Out-Null
318+
$switchBranchError = git checkout -q $branchName 2>&1 | Out-String
319319
if ($LASTEXITCODE -ne 0) {
320-
Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again."
320+
if ($switchBranchError) {
321+
Write-Error "Error: Branch '$branchName' exists but could not be checked out.`n$($switchBranchError.Trim())"
322+
} else {
323+
Write-Error "Error: Branch '$branchName' exists but could not be checked out. Resolve any uncommitted changes or conflicts and try again."
324+
}
321325
exit 1
322326
}
323327
}

tests/test_timestamp_branches.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
PROJECT_ROOT = Path(__file__).resolve().parent.parent
1616
CREATE_FEATURE = PROJECT_ROOT / "scripts" / "bash" / "create-new-feature.sh"
1717
CREATE_FEATURE_PS = PROJECT_ROOT / "scripts" / "powershell" / "create-new-feature.ps1"
18+
EXT_CREATE_FEATURE = (
19+
PROJECT_ROOT / "extensions" / "git" / "scripts" / "bash" / "create-new-feature.sh"
20+
)
21+
EXT_CREATE_FEATURE_PS = (
22+
PROJECT_ROOT / "extensions" / "git" / "scripts" / "powershell" / "create-new-feature.ps1"
23+
)
1824
COMMON_SH = PROJECT_ROOT / "scripts" / "bash" / "common.sh"
1925

2026

@@ -428,6 +434,43 @@ def test_allow_existing_no_git(self, no_git_dir: Path):
428434
)
429435
assert result.returncode == 0, result.stderr
430436

437+
def test_allow_existing_surfaces_checkout_error(self, git_repo: Path):
438+
"""Checkout failures on an existing branch should include Git's stderr."""
439+
shared_file = git_repo / "shared.txt"
440+
shared_file.write_text("base\n")
441+
subprocess.run(
442+
["git", "add", "shared.txt"],
443+
cwd=git_repo, check=True, capture_output=True,
444+
)
445+
subprocess.run(
446+
["git", "commit", "-m", "add shared file", "-q"],
447+
cwd=git_repo, check=True, capture_output=True,
448+
)
449+
subprocess.run(
450+
["git", "checkout", "-b", "010-checkout-failure"],
451+
cwd=git_repo, check=True, capture_output=True,
452+
)
453+
shared_file.write_text("branch version\n")
454+
subprocess.run(
455+
["git", "commit", "-am", "branch change", "-q"],
456+
cwd=git_repo, check=True, capture_output=True,
457+
)
458+
subprocess.run(
459+
["git", "checkout", "-"],
460+
cwd=git_repo, check=True, capture_output=True,
461+
)
462+
shared_file.write_text("uncommitted main change\n")
463+
464+
result = run_script(
465+
git_repo, "--allow-existing-branch", "--short-name", "checkout-failure",
466+
"--number", "10", "Checkout failure",
467+
)
468+
469+
assert result.returncode != 0, "checkout should fail with conflicting local changes"
470+
assert "Failed to switch to existing branch '010-checkout-failure'" in result.stderr
471+
assert "would be overwritten by checkout" in result.stderr
472+
assert "shared.txt" in result.stderr
473+
431474

432475
class TestAllowExistingBranchPowerShell:
433476
def test_powershell_supports_allow_existing_branch_flag(self):
@@ -437,6 +480,26 @@ def test_powershell_supports_allow_existing_branch_flag(self):
437480
# Ensure the flag is referenced in script logic, not just declared
438481
assert "AllowExistingBranch" in contents.replace("-AllowExistingBranch", "")
439482

483+
def test_powershell_surfaces_checkout_errors(self):
484+
"""Static guard: PS script preserves checkout stderr on existing-branch failures."""
485+
contents = CREATE_FEATURE_PS.read_text(encoding="utf-8")
486+
assert "$switchBranchError = git checkout -q $branchName 2>&1 | Out-String" in contents
487+
assert "exists but could not be checked out.`n$($switchBranchError.Trim())" in contents
488+
489+
490+
class TestGitExtensionParity:
491+
def test_bash_extension_surfaces_checkout_errors(self):
492+
"""Static guard: git extension bash script preserves checkout stderr."""
493+
contents = EXT_CREATE_FEATURE.read_text(encoding="utf-8")
494+
assert 'switch_branch_error=$(git checkout -q "$BRANCH_NAME" 2>&1)' in contents
495+
assert "Failed to switch to existing branch '$BRANCH_NAME'" in contents
496+
497+
def test_powershell_extension_surfaces_checkout_errors(self):
498+
"""Static guard: git extension PowerShell script preserves checkout stderr."""
499+
contents = EXT_CREATE_FEATURE_PS.read_text(encoding="utf-8")
500+
assert "$switchBranchError = git checkout -q $branchName 2>&1 | Out-String" in contents
501+
assert "exists but could not be checked out.`n$($switchBranchError.Trim())" in contents
502+
440503

441504
# ── Dry-Run Tests ────────────────────────────────────────────────────────────
442505

0 commit comments

Comments
 (0)