mirror of
https://github.com/github/spec-kit.git
synced 2026-04-02 18:53:09 +00:00
fix: support feature branch numbers with 4+ digits (#2040)
* fix: support feature branch numbers with 4+ digits in common.sh and common.ps1
The sequential feature number pattern was hardcoded to exactly 3 digits
(`{3}`), causing branches like `1234-feature-name` to be rejected.
Changed to `{3,}` (3 or more digits) to support growing projects.
Also added a guard to exclude malformed timestamp patterns from being
accepted as sequential prefixes.
Closes #344
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: narrow timestamp guard and use [long] to prevent overflow
- Change [int] to [long] in PowerShell Get-CurrentBranch to avoid
overflow for large feature numbers (>2,147,483,647)
- Narrow malformed-timestamp exclusion from ^[0-9]+-[0-9]{6}- to
^[0-9]{7}-[0-9]{6}- so valid sequential branches like
004-123456-fix-bug are not rejected
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression tests for 4+ digit feature branch support
Cover check_feature_branch and find_feature_dir_by_prefix with 4-digit
sequential prefixes, as requested in PR review #2040.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reject timestamp-like branches without trailing slug
Branches like "20260319-143022" (no "-<name>" suffix) were incorrectly
accepted as sequential prefixes. Add explicit rejection for 7-or-8
digit date + 6-digit time patterns with no trailing slug, in both
common.sh and common.ps1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
0945df9ec8
commit
8e14ab1935
@@ -78,7 +78,7 @@ get_current_branch() {
|
||||
latest_timestamp="$ts"
|
||||
latest_feature=$dirname
|
||||
fi
|
||||
elif [[ "$dirname" =~ ^([0-9]{3})- ]]; then
|
||||
elif [[ "$dirname" =~ ^([0-9]{3,})- ]]; then
|
||||
local number=${BASH_REMATCH[1]}
|
||||
number=$((10#$number))
|
||||
if [[ "$number" -gt "$highest" ]]; then
|
||||
@@ -124,9 +124,15 @@ check_feature_branch() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! "$branch" =~ ^[0-9]{3}- ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
|
||||
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
|
||||
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
|
||||
local is_sequential=false
|
||||
if [[ "$branch" =~ ^[0-9]{3,}- ]] && [[ ! "$branch" =~ ^[0-9]{7}-[0-9]{6}- ]] && [[ ! "$branch" =~ ^[0-9]{7,8}-[0-9]{6}$ ]]; then
|
||||
is_sequential=true
|
||||
fi
|
||||
if [[ "$is_sequential" != "true" ]] && [[ ! "$branch" =~ ^[0-9]{8}-[0-9]{6}- ]]; then
|
||||
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
||||
echo "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name" >&2
|
||||
echo "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -146,7 +152,7 @@ find_feature_dir_by_prefix() {
|
||||
local prefix=""
|
||||
if [[ "$branch_name" =~ ^([0-9]{8}-[0-9]{6})- ]]; then
|
||||
prefix="${BASH_REMATCH[1]}"
|
||||
elif [[ "$branch_name" =~ ^([0-9]{3})- ]]; then
|
||||
elif [[ "$branch_name" =~ ^([0-9]{3,})- ]]; then
|
||||
prefix="${BASH_REMATCH[1]}"
|
||||
else
|
||||
# If branch doesn't have a recognized prefix, fall back to exact match
|
||||
|
||||
@@ -83,8 +83,8 @@ function Get-CurrentBranch {
|
||||
$latestTimestamp = $ts
|
||||
$latestFeature = $_.Name
|
||||
}
|
||||
} elseif ($_.Name -match '^(\d{3})-') {
|
||||
$num = [int]$matches[1]
|
||||
} elseif ($_.Name -match '^(\d{3,})-') {
|
||||
$num = [long]$matches[1]
|
||||
if ($num -gt $highest) {
|
||||
$highest = $num
|
||||
# Only update if no timestamp branch found yet
|
||||
@@ -139,9 +139,13 @@ function Test-FeatureBranch {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($Branch -notmatch '^[0-9]{3}-' -and $Branch -notmatch '^\d{8}-\d{6}-') {
|
||||
# Accept sequential prefix (3+ digits) but exclude malformed timestamps
|
||||
# Malformed: 7-or-8 digit date + 6-digit time with no trailing slug (e.g. "2026031-143022" or "20260319-143022")
|
||||
$hasMalformedTimestamp = ($Branch -match '^[0-9]{7}-[0-9]{6}-') -or ($Branch -match '^(?:\d{7}|\d{8})-\d{6}$')
|
||||
$isSequential = ($Branch -match '^[0-9]{3,}-') -and (-not $hasMalformedTimestamp)
|
||||
if (-not $isSequential -and $Branch -notmatch '^\d{8}-\d{6}-') {
|
||||
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
||||
Write-Output "Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name"
|
||||
Write-Output "Feature branches should be named like: 001-feature-name, 1234-feature-name, or 20260319-143022-feature-name"
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
|
||||
@@ -186,11 +186,26 @@ class TestCheckFeatureBranch:
|
||||
result = source_and_call('check_feature_branch "main" "true"')
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_accepts_four_digit_sequential_branch(self):
|
||||
"""check_feature_branch accepts 4+ digit sequential branch."""
|
||||
result = source_and_call('check_feature_branch "1234-feat" "true"')
|
||||
assert result.returncode == 0
|
||||
|
||||
def test_rejects_partial_timestamp(self):
|
||||
"""Test 9: check_feature_branch rejects 7-digit date."""
|
||||
result = source_and_call('check_feature_branch "2026031-143022-feat" "true"')
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_rejects_timestamp_without_slug(self):
|
||||
"""check_feature_branch rejects timestamp-like branch missing trailing slug."""
|
||||
result = source_and_call('check_feature_branch "20260319-143022" "true"')
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_rejects_7digit_timestamp_without_slug(self):
|
||||
"""check_feature_branch rejects 7-digit date + 6-digit time without slug."""
|
||||
result = source_and_call('check_feature_branch "2026031-143022" "true"')
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
# ── find_feature_dir_by_prefix Tests ─────────────────────────────────────────
|
||||
|
||||
@@ -214,6 +229,15 @@ class TestFindFeatureDirByPrefix:
|
||||
assert result.returncode == 0
|
||||
assert result.stdout.strip() == f"{tmp_path}/specs/20260319-143022-original-feat"
|
||||
|
||||
def test_four_digit_sequential_prefix(self, tmp_path: Path):
|
||||
"""find_feature_dir_by_prefix resolves 4+ digit sequential prefix."""
|
||||
(tmp_path / "specs" / "1000-original-feat").mkdir(parents=True)
|
||||
result = source_and_call(
|
||||
f'find_feature_dir_by_prefix "{tmp_path}" "1000-different-name"'
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert result.stdout.strip() == f"{tmp_path}/specs/1000-original-feat"
|
||||
|
||||
|
||||
# ── get_current_branch Tests ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user