Skip to content

Commit c171cb7

Browse files
committed
fix: derive paths-only branch from feature directory
1 parent 2dd1ca4 commit c171cb7

3 files changed

Lines changed: 100 additions & 7 deletions

File tree

scripts/bash/common.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ get_feature_paths() {
151151

152152
# Use printf '%q' to safely quote values, preventing shell injection
153153
# via crafted branch names or paths containing special characters
154+
if [[ -z "$current_branch" ]]; then
155+
current_branch="${feature_dir%"${feature_dir##*[!\\/]}"}"
156+
current_branch="${current_branch##*[\\/]}"
157+
fi
154158
printf 'REPO_ROOT=%q\n' "$repo_root"
155159
printf 'CURRENT_BRANCH=%q\n' "$current_branch"
156160
printf 'FEATURE_DIR=%q\n' "$feature_dir"

scripts/powershell/common.ps1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ function Get-FeaturePathsEnv {
139139
[Console]::Error.WriteLine("ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY or run the specify command to create .specify/feature.json.")
140140
exit 1
141141
}
142+
143+
if (-not $currentBranch) {
144+
$normalizedFeatureDir = $featureDir.TrimEnd(
145+
[System.IO.Path]::DirectorySeparatorChar,
146+
[System.IO.Path]::AltDirectorySeparatorChar
147+
)
148+
$currentBranch = Split-Path -Path $normalizedFeatureDir -Leaf
149+
}
142150

143151
[PSCustomObject]@{
144152
REPO_ROOT = $repoRoot

tests/test_check_prerequisites_paths_only.py

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
CHECK_PREREQS_PS = PROJECT_ROOT / "scripts" / "powershell" / "check-prerequisites.ps1"
1818

1919
HAS_PWSH = shutil.which("pwsh") is not None
20-
_WINDOWS_POWERSHELL = (shutil.which("powershell.exe") or shutil.which("powershell")) if os.name == "nt" else None
20+
_WINDOWS_POWERSHELL = (
21+
(shutil.which("powershell.exe") or shutil.which("powershell"))
22+
if os.name == "nt"
23+
else None
24+
)
2125

2226

2327
def _install_bash_scripts(repo: Path) -> None:
@@ -141,6 +145,46 @@ def test_paths_only_text_mode_on_non_spec_branch(prereq_repo: Path) -> None:
141145
assert "FEATURE_DIR:" in result.stdout
142146

143147

148+
@requires_bash
149+
def test_paths_only_falls_back_to_feature_dir_basename(prereq_repo: Path) -> None:
150+
"""--paths-only should emit a non-empty branch name from feature.json."""
151+
feat = prereq_repo / "specs" / "001-my-feature"
152+
feat.mkdir(parents=True, exist_ok=True)
153+
_write_feature_json(prereq_repo)
154+
script = prereq_repo / ".specify" / "scripts" / "bash" / "check-prerequisites.sh"
155+
result = subprocess.run(
156+
["bash", str(script), "--json", "--paths-only"],
157+
cwd=prereq_repo,
158+
capture_output=True,
159+
text=True,
160+
check=False,
161+
env=_clean_env(),
162+
)
163+
assert result.returncode == 0, result.stderr
164+
data = json.loads(result.stdout)
165+
assert data["BRANCH"] == "001-my-feature"
166+
167+
168+
@requires_bash
169+
def test_paths_only_fallback_handles_windows_style_feature_dir(
170+
prereq_repo: Path,
171+
) -> None:
172+
"""--paths-only should derive BRANCH from backslash-separated feature dirs."""
173+
_write_feature_json(prereq_repo, "specs\\001-my-feature")
174+
script = prereq_repo / ".specify" / "scripts" / "bash" / "check-prerequisites.sh"
175+
result = subprocess.run(
176+
["bash", str(script), "--json", "--paths-only"],
177+
cwd=prereq_repo,
178+
capture_output=True,
179+
text=True,
180+
check=False,
181+
env=_clean_env(),
182+
)
183+
assert result.returncode == 0, result.stderr
184+
data = json.loads(result.stdout)
185+
assert data["BRANCH"] == "001-my-feature"
186+
187+
144188
@requires_bash
145189
def test_normal_mode_still_validates_branch(prereq_repo: Path) -> None:
146190
"""Without --paths-only, feature directory validation must still fail on main."""
@@ -160,13 +204,17 @@ def test_normal_mode_still_validates_branch(prereq_repo: Path) -> None:
160204
# ── PowerShell tests ──────────────────────────────────────────────────────
161205

162206

163-
@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
207+
@pytest.mark.skipif(
208+
not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available"
209+
)
164210
def test_ps_paths_only_succeeds_on_non_spec_branch(prereq_repo: Path) -> None:
165211
"""-PathsOnly must return paths when feature.json pins the feature dir."""
166212
feat = prereq_repo / "specs" / "001-my-feature"
167213
feat.mkdir(parents=True, exist_ok=True)
168214
_write_feature_json(prereq_repo)
169-
script = prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
215+
script = (
216+
prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
217+
)
170218
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
171219
result = subprocess.run(
172220
[exe, "-NoProfile", "-File", str(script), "-Json", "-PathsOnly"],
@@ -183,7 +231,9 @@ def test_ps_paths_only_succeeds_on_non_spec_branch(prereq_repo: Path) -> None:
183231
assert "FEATURE_DIR" in data
184232

185233

186-
@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
234+
@pytest.mark.skipif(
235+
not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available"
236+
)
187237
def test_ps_paths_only_succeeds_on_spec_branch(prereq_repo: Path) -> None:
188238
"""-PathsOnly must also work when feature.json and SPECIFY_FEATURE agree."""
189239
subprocess.run(
@@ -194,7 +244,9 @@ def test_ps_paths_only_succeeds_on_spec_branch(prereq_repo: Path) -> None:
194244
feat = prereq_repo / "specs" / "001-my-feature"
195245
feat.mkdir(parents=True, exist_ok=True)
196246
_write_feature_json(prereq_repo)
197-
script = prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
247+
script = (
248+
prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
249+
)
198250
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
199251
env = _clean_env()
200252
env["SPECIFY_FEATURE"] = "001-my-feature"
@@ -211,10 +263,39 @@ def test_ps_paths_only_succeeds_on_spec_branch(prereq_repo: Path) -> None:
211263
assert "FEATURE_DIR" in data
212264

213265

214-
@pytest.mark.skipif(not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available")
266+
@pytest.mark.skipif(
267+
not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available"
268+
)
269+
def test_ps_paths_only_falls_back_to_feature_dir_basename(prereq_repo: Path) -> None:
270+
"""-PathsOnly should emit a non-empty branch name from feature.json."""
271+
feat = prereq_repo / "specs" / "001-my-feature"
272+
feat.mkdir(parents=True, exist_ok=True)
273+
_write_feature_json(prereq_repo)
274+
script = (
275+
prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
276+
)
277+
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
278+
result = subprocess.run(
279+
[exe, "-NoProfile", "-File", str(script), "-Json", "-PathsOnly"],
280+
cwd=prereq_repo,
281+
capture_output=True,
282+
text=True,
283+
check=False,
284+
env=_clean_env(),
285+
)
286+
assert result.returncode == 0, result.stderr
287+
data = json.loads(result.stdout)
288+
assert data["BRANCH"] == "001-my-feature"
289+
290+
291+
@pytest.mark.skipif(
292+
not (HAS_PWSH or _WINDOWS_POWERSHELL), reason="no PowerShell available"
293+
)
215294
def test_ps_normal_mode_still_validates_branch(prereq_repo: Path) -> None:
216295
"""Without -PathsOnly, feature directory validation must still fail on main."""
217-
script = prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
296+
script = (
297+
prereq_repo / ".specify" / "scripts" / "powershell" / "check-prerequisites.ps1"
298+
)
218299
exe = "pwsh" if HAS_PWSH else _WINDOWS_POWERSHELL
219300
result = subprocess.run(
220301
[exe, "-NoProfile", "-File", str(script), "-Json"],

0 commit comments

Comments
 (0)