From b40b41cf50822e803626dd528f1177ff552ea5b9 Mon Sep 17 00:00:00 2001 From: Simon Gent Date: Thu, 23 Oct 2025 12:25:31 +0100 Subject: [PATCH] fix: improve branch number detection to check all sources - Use git ls-remote for more reliable remote branch detection - Check remote branches, local branches, AND specs directories - Match exact short-name pattern to avoid false positives - Ensures no duplicate numbers across all sources --- scripts/bash/create-new-feature.sh | 17 ++++++-- scripts/powershell/create-new-feature.ps1 | 50 ++++++++++++++++++----- templates/commands/specify.md | 17 ++++---- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh index 714927f2..86d9ecf8 100644 --- a/scripts/bash/create-new-feature.sh +++ b/scripts/bash/create-new-feature.sh @@ -87,12 +87,21 @@ check_existing_branches() { # Fetch all remotes to get latest branch info (suppress errors if no remotes) git fetch --all --prune 2>/dev/null || true - # Find all branches matching the pattern (local and remote) - local branches=$(git branch -a 2>/dev/null | grep -E "feature/[0-9]+-${short_name}$" | sed 's/.*feature\///' | sed "s/-${short_name}$//" | sort -n) + # Find all branches matching the pattern using git ls-remote (more reliable) + 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) - # Get the highest number + # Also check local branches + local local_branches=$(git branch 2>/dev/null | grep -E "^[* ]*[0-9]+-${short_name}$" | sed 's/^[* ]*//' | sed 's/-.*//' | sort -n) + + # Check specs directory as well + local spec_dirs="" + if [ -d "$SPECS_DIR" ]; 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) + fi + + # Combine all sources and get the highest number local max_num=0 - for num in $branches; do + for num in $remote_branches $local_branches $spec_dirs; do if [ "$num" -gt "$max_num" ]; then max_num=$num fi diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1 index f3754725..4daa6d2c 100644 --- a/scripts/powershell/create-new-feature.ps1 +++ b/scripts/powershell/create-new-feature.ps1 @@ -61,7 +61,8 @@ function Find-RepositoryRoot { function Get-NextBranchNumber { param( - [string]$ShortName + [string]$ShortName, + [string]$SpecsDir ) # Fetch all remotes to get latest branch info (suppress errors if no remotes) @@ -71,13 +72,13 @@ function Get-NextBranchNumber { # Ignore fetch errors } - # Find all branches matching the pattern (local and remote) - $branches = @() + # Find remote branches matching the pattern using git ls-remote + $remoteBranches = @() try { - $allBranches = git branch -a 2>$null - if ($allBranches) { - $branches = $allBranches | Where-Object { $_ -match "feature/(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object { - if ($_ -match "feature/(\d+)-") { + $remoteRefs = git ls-remote --heads origin 2>$null + if ($remoteRefs) { + $remoteBranches = $remoteRefs | Where-Object { $_ -match "refs/heads/(\d+)-$([regex]::Escape($ShortName))$" } | ForEach-Object { + if ($_ -match "refs/heads/(\d+)-") { [int]$matches[1] } } @@ -86,9 +87,38 @@ function Get-NextBranchNumber { # Ignore errors } - # Get the highest number + # 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 $branches) { + foreach ($num in ($remoteBranches + $localBranches + $specDirs)) { if ($num -gt $maxNum) { $maxNum = $num } @@ -178,7 +208,7 @@ if ($ShortName) { if ($Number -eq 0) { if ($hasGit) { # Check existing branches on remotes - $Number = Get-NextBranchNumber -ShortName $branchSuffix + $Number = Get-NextBranchNumber -ShortName $branchSuffix -SpecsDir $specsDir } else { # Fall back to local directory check $highest = 0 diff --git a/templates/commands/specify.md b/templates/commands/specify.md index c3e6b37c..ba8c1eb9 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -38,14 +38,14 @@ Given that feature description, do this: git fetch --all --prune ``` - b. List all branches (local and remote) that match the short-name pattern: - ```bash - git branch -a | grep -E "feature/[0-9]+-$" - ``` + b. Find the highest feature number across all sources for the short-name: + - Remote branches: `git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-$'` + - Local branches: `git branch | grep -E '^[* ]*[0-9]+-$'` + - Specs directories: Check for directories matching `specs/[0-9]+-` c. Determine the next available number: - - Extract all numbers from existing branches (both local and remote) - - Find the highest number N from branches that exist + - Extract all numbers from all three sources + - Find the highest number N - Use N+1 for the new branch number d. Run the script `{SCRIPT}` with the calculated number and short-name: @@ -54,8 +54,9 @@ Given that feature description, do this: - PowerShell example: `{SCRIPT} -Json -Number 5 -ShortName "user-auth" "Add user authentication"` **IMPORTANT**: - - Only consider branches that still exist (local or remote) - - If no existing branches found with this short-name, start with number 1 + - Check all three sources (remote branches, local branches, specs directories) to find the highest number + - Only match branches/directories with the exact short-name pattern + - If no existing branches/directories found with this short-name, start with number 1 - The JSON output will contain BRANCH_NAME and SPEC_FILE paths - You must only ever run this script once per feature - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot")