fix: use global maximum for branch numbering to prevent collisions

The check_existing_branches (bash) and Get-NextBranchNumber (PowerShell)
functions were only looking for branches/specs matching the SAME short name
when determining the next feature number. This caused collisions where
multiple features could be assigned the same number if they had different
short names.

For example, if feature 023-ci-optimization existed, creating a new feature
with a different short name would incorrectly use 001 instead of 024.

This fix changes both functions to:
1. Use get_highest_from_branches() / Get-HighestNumberFromBranches to find
   the highest number across ALL branches globally
2. Use get_highest_from_specs() / Get-HighestNumberFromSpecs to find the
   highest number across ALL spec directories globally
3. Return the maximum of both + 1

The helper functions already existed but were not being used. This fix
properly utilizes them to ensure features are numbered sequentially
regardless of their short names.

Issue: Branch number collisions when creating features with different names
Impact: Prevents multiple features from sharing the same number prefix
This commit is contained in:
Joseph Mearman
2025-11-23 16:01:56 +00:00
parent f205fa3b58
commit 33df8976ca
2 changed files with 24 additions and 75 deletions

View File

@@ -130,30 +130,22 @@ get_highest_from_branches() {
check_existing_branches() { check_existing_branches() {
local short_name="$1" local short_name="$1"
local specs_dir="$2" local specs_dir="$2"
# Fetch all remotes to get latest branch info (suppress errors if no remotes) # Fetch all remotes to get latest branch info (suppress errors if no remotes)
git fetch --all --prune 2>/dev/null || true git fetch --all --prune 2>/dev/null || true
# Find all branches matching the pattern using git ls-remote (more reliable) # Get highest number from ALL branches (not just matching short name)
local remote_branches=$(git ls-remote --heads origin 2>/dev/null | grep -E "refs/heads/[0-9]+-${short_name}$" | sed 's/.*\/\([0-9]*\)-.*/\1/' | sort -n) local highest_branch=$(get_highest_from_branches)
# Also check local branches # Get highest number from ALL specs (not just matching short name)
local local_branches=$(git branch 2>/dev/null | grep -E "^[* ]*[0-9]+-${short_name}$" | sed 's/^[* ]*//' | sed 's/-.*//' | sort -n) local highest_spec=$(get_highest_from_specs "$specs_dir")
# Check specs directory as well # Take the maximum of both
local spec_dirs="" local max_num=$highest_branch
if [ -d "$specs_dir" ]; then if [ "$highest_spec" -gt "$max_num" ]; then
spec_dirs=$(find "$specs_dir" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n) max_num=$highest_spec
fi fi
# Combine all sources and get the highest number
local max_num=0
for num in $remote_branches $local_branches $spec_dirs; do
if [ "$num" -gt "$max_num" ]; then
max_num=$num
fi
done
# Return next number # Return next number
echo $((max_num + 1)) echo $((max_num + 1))
} }

View File

@@ -104,66 +104,23 @@ function Get-NextBranchNumber {
[string]$ShortName, [string]$ShortName,
[string]$SpecsDir [string]$SpecsDir
) )
# Fetch all remotes to get latest branch info (suppress errors if no remotes) # Fetch all remotes to get latest branch info (suppress errors if no remotes)
try { try {
git fetch --all --prune 2>$null | Out-Null git fetch --all --prune 2>$null | Out-Null
} catch { } catch {
# Ignore fetch errors # Ignore fetch errors
} }
# Find remote branches matching the pattern using git ls-remote # Get highest number from ALL branches (not just matching short name)
$remoteBranches = @() $highestBranch = Get-HighestNumberFromBranches
try {
$remoteRefs = git ls-remote --heads origin 2>$null # Get highest number from ALL specs (not just matching short name)
if ($remoteRefs) { $highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir
$remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
if ($_ -match "refs/heads/(\d+)-") { # Take the maximum of both
[int]$matches[1] $maxNum = [Math]::Max($highestBranch, $highestSpec)
}
}
}
} catch {
# Ignore errors
}
# Check local branches
$localBranches = @()
try {
$allBranches = git branch 2>$null
if ($allBranches) {
$localBranches = $allBranches | Where-Object { $_ -match "^\*?\s*(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
if ($_ -match "(\d+)-") {
[int]$matches[1]
}
}
}
} catch {
# Ignore errors
}
# Check specs directory
$specDirs = @()
if (Test-Path $SpecsDir) {
try {
$specDirs = Get-ChildItem -Path $SpecsDir -Directory | Where-Object { $_.Name -match "^(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object {
if ($_.Name -match "^(\d+)-") {
[int]$matches[1]
}
}
} catch {
# Ignore errors
}
}
# Combine all sources and get the highest number
$maxNum = 0
foreach ($num in ($remoteBranches + $localBranches + $specDirs)) {
if ($num -gt $maxNum) {
$maxNum = $num
}
}
# Return next number # Return next number
return $maxNum + 1 return $maxNum + 1
} }