Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions scripts/bash/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ get_feature_paths() {
return 1
fi

# When no branch context exists (no SPECIFY_FEATURE, feature resolved via
# SPECIFY_FEATURE_DIRECTORY or feature.json), fall back to the feature
# directory basename so CURRENT_BRANCH is a usable identifier rather than
# an empty, misleading value (issue #3026).
if [[ -z "$current_branch" ]]; then
local feature_dir_trimmed="${feature_dir%/}"
current_branch="${feature_dir_trimmed##*/}"
fi

# Use printf '%q' to safely quote values, preventing shell injection
# via crafted branch names or paths containing special characters
printf 'REPO_ROOT=%q\n' "$repo_root"
Expand Down
9 changes: 9 additions & 0 deletions scripts/powershell/common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ function Get-FeaturePathsEnv {
exit 1
}

# When no branch context exists (no SPECIFY_FEATURE, feature resolved via
# SPECIFY_FEATURE_DIRECTORY or feature.json), fall back to the feature
# directory basename so CURRENT_BRANCH is a usable identifier rather than
# an empty, misleading value (issue #3026).
if (-not $currentBranch) {
$featureDirTrimmed = [System.IO.Path]::TrimEndingDirectorySeparator($featureDir)
$currentBranch = Split-Path -Leaf $featureDirTrimmed
}

[PSCustomObject]@{
REPO_ROOT = $repoRoot
CURRENT_BRANCH = $currentBranch
Expand Down
71 changes: 71 additions & 0 deletions tests/test_check_prerequisites_paths_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,50 @@ def test_paths_only_succeeds_on_spec_branch(prereq_repo: Path) -> None:
assert "001-my-feature" in data.get("BRANCH", "")


@requires_bash
def test_current_branch_falls_back_to_feature_dir_basename(prereq_repo: Path) -> None:
"""When no branch context exists (feature resolved from feature.json,
SPECIFY_FEATURE unset), BRANCH must fall back to the feature directory
basename instead of being emitted empty (#3026)."""
feat = prereq_repo / "specs" / "001-my-feature"
feat.mkdir(parents=True, exist_ok=True)
_write_feature_json(prereq_repo)
script = prereq_repo / ".specify" / "scripts" / "bash" / "check-prerequisites.sh"
result = subprocess.run(
["bash", str(script), "--json", "--paths-only"],
cwd=prereq_repo,
capture_output=True,
text=True,
check=False,
env=_clean_env(), # no SPECIFY_FEATURE
)
assert result.returncode == 0, result.stderr
data = json.loads(result.stdout)
assert data["BRANCH"] == "001-my-feature"


@requires_bash
def test_explicit_feature_still_overrides_basename(prereq_repo: Path) -> None:
"""SPECIFY_FEATURE remains authoritative over the basename fallback (#3026)."""
feat = prereq_repo / "specs" / "001-my-feature"
feat.mkdir(parents=True, exist_ok=True)
_write_feature_json(prereq_repo)
script = prereq_repo / ".specify" / "scripts" / "bash" / "check-prerequisites.sh"
env = _clean_env()
env["SPECIFY_FEATURE"] = "my-explicit-branch"
result = subprocess.run(
["bash", str(script), "--json", "--paths-only"],
cwd=prereq_repo,
capture_output=True,
text=True,
check=False,
env=env,
)
assert result.returncode == 0, result.stderr
data = json.loads(result.stdout)
assert data["BRANCH"] == "my-explicit-branch"


@requires_bash
def test_paths_only_text_mode_on_non_spec_branch(prereq_repo: Path) -> None:
"""--paths-only without --json must return text paths from feature.json."""
Expand Down Expand Up @@ -189,6 +233,33 @@ def test_ps_paths_only_succeeds_on_non_spec_branch(prereq_repo: Path) -> None:
assert "FEATURE_DIR" in data


@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
@pytest.mark.parametrize("use_env_var", [False, True], ids=["feature_json", "env_var"])
def test_ps_current_branch_falls_back_to_feature_dir_basename(prereq_repo: Path, use_env_var: bool) -> None:
"""With no SPECIFY_FEATURE, BRANCH falls back to the feature directory
basename (from feature.json or SPECIFY_FEATURE_DIRECTORY) instead of being
emitted empty (#3026)."""
feat = prereq_repo / "specs" / "001-my-feature"
feat.mkdir(parents=True, exist_ok=True)
env = _clean_env() # no SPECIFY_FEATURE
if use_env_var:
env["SPECIFY_FEATURE_DIRECTORY"] = "specs/001-my-feature"
else:
_write_feature_json(prereq_repo)
script = prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
result = subprocess.run(
[exe, "-NoProfile", "-File", str(script), "-Json", "-PathsOnly"],
cwd=prereq_repo,
capture_output=True,
text=True,
check=False,
env=env,
)
assert result.returncode == 0, result.stderr
data = json.loads(result.stdout)
assert data["BRANCH"] == "001-my-feature"

Comment on lines +236 to +262
@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
def test_ps_paths_only_succeeds_on_spec_branch(prereq_repo: Path) -> None:
"""-PathsOnly must also work when feature.json and SPECIFY_FEATURE agree."""
Expand Down
Loading