-
Notifications
You must be signed in to change notification settings - Fork 7.3k
fix(scripts): validate --number flag against existing branches/specs #2036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d66aa26
e602275
2f7d8f3
59153f7
6cf7bd1
a8e710e
135163b
2495abe
29302c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -249,8 +249,17 @@ if [ "$USE_TIMESTAMP" = true ]; then | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FEATURE_NUM=$(date +%Y%m%d-%H%M%S) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate --number input when provided | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -n "$BRANCH_NUMBER" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! echo "$BRANCH_NUMBER" | grep -Eq '^[0-9]+$' || [ "$((10#$BRANCH_NUMBER))" -lt 1 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| >&2 echo "Error: --number requires a positive integer, got '$BRANCH_NUMBER'" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Determine branch number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -z "$BRANCH_NUMBER" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # No manual number provided -- auto-detect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$HAS_GIT" = true ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check existing branches on remotes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -259,6 +268,49 @@ else | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HIGHEST=$(get_highest_from_specs "$SPECS_DIR") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BRANCH_NUMBER=$((HIGHEST + 1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif [ "$ALLOW_EXISTING" = false ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Manual number provided -- validate it is not already in use | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # (skip when --allow-existing-branch, as the caller intentionally targets an existing branch) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MANUAL_NUM=$((10#$BRANCH_NUMBER)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MANUAL_NUM_PADDED=$(printf "%03d" "$MANUAL_NUM") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NUMBER_IN_USE=false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check specs directory for collision | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -d "$SPECS_DIR" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for dir in "$SPECS_DIR"/*/; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ -d "$dir" ] || continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dirname=$(basename "$dir") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if echo "$dirname" | grep -q "^${MANUAL_NUM_PADDED}-"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NUMBER_IN_USE=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check git branches for collision (fetch to catch remote-only branches) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$NUMBER_IN_USE" = false ] && [ "$HAS_GIT" = true ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| git fetch --all --prune 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| branches=$(git branch -a 2>/dev/null || echo "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -n "$branches" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r branch; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if echo "$clean_branch" | grep -q "^${MANUAL_NUM_PADDED}-"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NUMBER_IN_USE=true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done <<< "$branches" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$NUMBER_IN_USE" = true ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| >&2 echo "Warning: --number $BRANCH_NUMBER conflicts with existing branch/spec (${MANUAL_NUM_PADDED}-*). Auto-detecting next available number." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$HAS_GIT" = true ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+305
to
+308
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$NUMBER_IN_USE" = true ]; then | |
| >&2 echo "Warning: --number $BRANCH_NUMBER conflicts with existing branch/spec (${MANUAL_NUM_PADDED}-*). Auto-detecting next available number." | |
| if [ "$HAS_GIT" = true ]; then | |
| BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") | |
| compute_next_branch_number_from_cache() { | |
| # Compute the next available branch number using existing specs and an | |
| # already-fetched list of git branches (to avoid a redundant git fetch). | |
| local specs_dir="$1" | |
| local branches_input="$2" | |
| local highest max_num | |
| # Start from the highest number found in the specs directory. | |
| highest=$(get_highest_from_specs "$specs_dir") | |
| max_num="$highest" | |
| # If we have a cached branch list, incorporate it to find the true max. | |
| if [ -n "$branches_input" ]; then | |
| while IFS= read -r branch; do | |
| # Normalize branch name: strip leading markers and remote prefix. | |
| local clean_branch | |
| clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||') | |
| # Extract numeric prefix (e.g., "012" from "012-something"). | |
| local num_part | |
| num_part=${clean_branch%%-*} | |
| # Ensure prefix is numeric before comparing. | |
| if echo "$num_part" | grep -Eq '^[0-9]+$'; then | |
| local num_value | |
| num_value=$((10#$num_part)) | |
| if [ "$num_value" -gt "$max_num" ]; then | |
| max_num="$num_value" | |
| fi | |
| fi | |
| done <<< "$branches_input" | |
| fi | |
| echo $((max_num + 1)) | |
| } | |
| if [ "$NUMBER_IN_USE" = true ]; then | |
| >&2 echo "Warning: --number $BRANCH_NUMBER conflicts with existing branch/spec (${MANUAL_NUM_PADDED}-*). Auto-detecting next available number." | |
| if [ "$HAS_GIT" = true ]; then | |
| if [ -n "${branches:-}" ]; then | |
| # Reuse the already-fetched branch list to avoid a redundant git fetch. | |
| BRANCH_NUMBER=$(compute_next_branch_number_from_cache "$SPECS_DIR" "$branches") | |
| else | |
| # No cached branches available; fall back to the existing behavior. | |
| BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") | |
| fi |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -201,15 +201,65 @@ if ($Timestamp) { | |||||
| $featureNum = Get-Date -Format 'yyyyMMdd-HHmmss' | ||||||
| $branchName = "$featureNum-$branchSuffix" | ||||||
| } else { | ||||||
| # Validate -Number input when explicitly provided | ||||||
| if ($PSBoundParameters.ContainsKey('Number') -and $Number -lt 1) { | ||||||
| Write-Error "-Number requires a positive integer, got $Number" | ||||||
| exit 1 | ||||||
| } | ||||||
|
|
||||||
| # Determine branch number | ||||||
| if ($Number -eq 0) { | ||||||
| # No manual number provided -- auto-detect | ||||||
| if ($hasGit) { | ||||||
| # Check existing branches on remotes | ||||||
| $Number = Get-NextBranchNumber -SpecsDir $specsDir | ||||||
| } else { | ||||||
| # Fall back to local directory check | ||||||
| $Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1 | ||||||
| } | ||||||
| } elseif (-not $AllowExistingBranch) { | ||||||
| # Manual number provided -- validate it is not already in use | ||||||
| # (skip when -AllowExistingBranch, as the caller intentionally targets an existing branch) | ||||||
| $manualNumPadded = '{0:000}' -f $Number | ||||||
| $numberInUse = $false | ||||||
mnriem marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| # Check specs directory for collision | ||||||
| if (Test-Path $specsDir) { | ||||||
| foreach ($dir in (Get-ChildItem -Path $specsDir -Directory)) { | ||||||
| if ($dir.Name -match "^$manualNumPadded-") { | ||||||
| $numberInUse = $true | ||||||
| break | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| # Check git branches for collision (fetch to catch remote-only branches) | ||||||
| if (-not $numberInUse -and $hasGit) { | ||||||
| try { | ||||||
| git fetch --all --prune 2>$null | Out-Null | ||||||
| $branches = git branch -a 2>$null | ||||||
| if ($LASTEXITCODE -eq 0 -and $branches) { | ||||||
| foreach ($branch in $branches) { | ||||||
| $cleanBranch = $branch.Trim() -replace '^\*?\s*', '' -replace '^remotes/[^/]+/', '' | ||||||
| if ($cleanBranch -match "^$manualNumPadded-") { | ||||||
| $numberInUse = $true | ||||||
| break | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } catch { | ||||||
| Write-Verbose "Could not check Git branches: $_" | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if ($numberInUse) { | ||||||
| Write-Warning "-Number $Number conflicts with existing branch/spec ($manualNumPadded-*). Auto-detecting next available number." | ||||||
|
||||||
| Write-Warning "-Number $Number conflicts with existing branch/spec ($manualNumPadded-*). Auto-detecting next available number." | |
| Write-Warning "[specify] Warning: -Number $Number conflicts with existing branch/spec ($manualNumPadded-*). Auto-detecting next available number." |
mnriem marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the collision path, the script has already executed git fetch --all --prune (above) to validate $Number, but then calls Get-NextBranchNumber, which performs another git fetch --all --prune. This duplicates network work and can add noticeable latency/hangs when the user hits a collision (the scenario this validation is targeting). Consider reusing the already-fetched branch/spec scan results or adding a -SkipFetch option to Get-NextBranchNumber for callers that already fetched.
Uh oh!
There was an error while loading. Please reload this page.