Skip to content

Commit 8d4710d

Browse files
committed
fix(scripts): deduplicate number extraction and branch scanning logic
Extract shared _extract_highest_number helper (bash) and Get-HighestNumberFromNames (PowerShell) to eliminate duplicated number extraction patterns between local branch and remote ref scanning. Add SkipFetch/skip_fetch parameter to check_existing_branches / Get-NextBranchNumber so dry-run reuses the same function instead of inlining duplicate max-of-branches-and-specs logic. Assisted-By: 🤖 Claude Code
1 parent 71e0b8f commit 8d4710d

File tree

2 files changed

+93
-100
lines changed

2 files changed

+93
-100
lines changed

scripts/bash/create-new-feature.sh

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -115,27 +115,23 @@ get_highest_from_specs() {
115115

116116
# Function to get highest number from git branches
117117
get_highest_from_branches() {
118+
git branch -a 2>/dev/null | sed 's/^[* ]*//; s|^remotes/[^/]*/||' | _extract_highest_number
119+
}
120+
121+
# Extract the highest sequential feature number from a list of ref names (one per line).
122+
# Shared by get_highest_from_branches and get_highest_from_remote_refs.
123+
_extract_highest_number() {
118124
local highest=0
119-
120-
# Get all branches (local and remote)
121-
branches=$(git branch -a 2>/dev/null || echo "")
122-
123-
if [ -n "$branches" ]; then
124-
while IFS= read -r branch; do
125-
# Clean branch name: remove leading markers and remote prefixes
126-
clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||')
127-
128-
# Extract sequential feature number (>=3 digits), skip timestamp branches.
129-
if echo "$clean_branch" | grep -Eq '^[0-9]{3,}-' && ! echo "$clean_branch" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
130-
number=$(echo "$clean_branch" | grep -Eo '^[0-9]+' || echo "0")
131-
number=$((10#$number))
132-
if [ "$number" -gt "$highest" ]; then
133-
highest=$number
134-
fi
125+
while IFS= read -r name; do
126+
[ -z "$name" ] && continue
127+
if echo "$name" | grep -Eq '^[0-9]{3,}-' && ! echo "$name" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
128+
number=$(echo "$name" | grep -Eo '^[0-9]+' || echo "0")
129+
number=$((10#$number))
130+
if [ "$number" -gt "$highest" ]; then
131+
highest=$number
135132
fi
136-
done <<< "$branches"
137-
fi
138-
133+
fi
134+
done
139135
echo "$highest"
140136
}
141137

@@ -144,32 +140,34 @@ get_highest_from_remote_refs() {
144140
local highest=0
145141

146142
for remote in $(git remote 2>/dev/null); do
147-
while IFS= read -r line; do
148-
[ -z "$line" ] && continue
149-
# Extract ref name from ls-remote output (hash\trefs/heads/branch-name)
150-
ref="${line##*/}"
151-
if echo "$ref" | grep -Eq '^[0-9]{3,}-' && ! echo "$ref" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
152-
number=$(echo "$ref" | grep -Eo '^[0-9]+' || echo "0")
153-
number=$((10#$number))
154-
if [ "$number" -gt "$highest" ]; then
155-
highest=$number
156-
fi
157-
fi
158-
done <<< "$(git ls-remote --heads "$remote" 2>/dev/null || echo "")"
143+
local remote_highest
144+
remote_highest=$(git ls-remote --heads "$remote" 2>/dev/null | sed 's|.*refs/heads/||' | _extract_highest_number)
145+
if [ "$remote_highest" -gt "$highest" ]; then
146+
highest=$remote_highest
147+
fi
159148
done
160149

161150
echo "$highest"
162151
}
163152

164-
# Function to check existing branches (local and remote) and return next available number
153+
# Function to check existing branches (local and remote) and return next available number.
154+
# When skip_fetch is true, queries remotes via ls-remote (read-only) instead of fetching.
165155
check_existing_branches() {
166156
local specs_dir="$1"
167-
168-
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
169-
git fetch --all --prune >/dev/null 2>&1 || true
170-
171-
# Get highest number from ALL branches (not just matching short name)
172-
local highest_branch=$(get_highest_from_branches)
157+
local skip_fetch="${2:-false}"
158+
159+
if [ "$skip_fetch" = true ]; then
160+
# Side-effect-free: query remotes via ls-remote
161+
local highest_remote=$(get_highest_from_remote_refs)
162+
local highest_branch=$(get_highest_from_branches)
163+
if [ "$highest_remote" -gt "$highest_branch" ]; then
164+
highest_branch=$highest_remote
165+
fi
166+
else
167+
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
168+
git fetch --all --prune >/dev/null 2>&1 || true
169+
local highest_branch=$(get_highest_from_branches)
170+
fi
173171

174172
# Get highest number from ALL specs (not just matching short name)
175173
local highest_spec=$(get_highest_from_specs "$specs_dir")
@@ -280,22 +278,13 @@ if [ "$USE_TIMESTAMP" = true ]; then
280278
else
281279
# Determine branch number
282280
if [ -z "$BRANCH_NUMBER" ]; then
283-
if [ "$DRY_RUN" = true ]; then
284-
# Dry-run: query remote refs without fetching (side-effect-free)
285-
_highest_branch=0
286-
if [ "$HAS_GIT" = true ]; then
287-
_highest_branch=$(get_highest_from_branches)
288-
_highest_remote=$(get_highest_from_remote_refs)
289-
if [ "$_highest_remote" -gt "$_highest_branch" ]; then
290-
_highest_branch=$_highest_remote
291-
fi
292-
fi
293-
_highest_spec=$(get_highest_from_specs "$SPECS_DIR")
294-
_max_num=$_highest_branch
295-
if [ "$_highest_spec" -gt "$_max_num" ]; then
296-
_max_num=$_highest_spec
297-
fi
298-
BRANCH_NUMBER=$((_max_num + 1))
281+
if [ "$DRY_RUN" = true ] && [ "$HAS_GIT" = true ]; then
282+
# Dry-run: query remotes via ls-remote (side-effect-free, no fetch)
283+
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR" true)
284+
elif [ "$DRY_RUN" = true ]; then
285+
# Dry-run without git: local spec dirs only
286+
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
287+
BRANCH_NUMBER=$((HIGHEST + 1))
299288
elif [ "$HAS_GIT" = true ]; then
300289
# Check existing branches on remotes
301290
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")

scripts/powershell/create-new-feature.ps1

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -67,31 +67,38 @@ function Get-HighestNumberFromSpecs {
6767
return $highest
6868
}
6969

70+
# Extract the highest sequential feature number from a list of branch/ref names.
71+
# Shared by Get-HighestNumberFromBranches and Get-HighestNumberFromRemoteRefs.
72+
function Get-HighestNumberFromNames {
73+
param([string[]]$Names)
74+
75+
[long]$highest = 0
76+
foreach ($name in $Names) {
77+
if ($name -match '^(\d{3,})-' -and $name -notmatch '^\d{8}-\d{6}-') {
78+
[long]$num = 0
79+
if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) {
80+
$highest = $num
81+
}
82+
}
83+
}
84+
return $highest
85+
}
86+
7087
function Get-HighestNumberFromBranches {
7188
param()
7289

73-
[long]$highest = 0
7490
try {
7591
$branches = git branch -a 2>$null
76-
if ($LASTEXITCODE -eq 0) {
77-
foreach ($branch in $branches) {
78-
# Clean branch name: remove leading markers and remote prefixes
79-
$cleanBranch = $branch.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', ''
80-
81-
# Extract sequential feature number (>=3 digits), skip timestamp branches.
82-
if ($cleanBranch -match '^(\d{3,})-' -and $cleanBranch -notmatch '^\d{8}-\d{6}-') {
83-
[long]$num = 0
84-
if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) {
85-
$highest = $num
86-
}
87-
}
92+
if ($LASTEXITCODE -eq 0 -and $branches) {
93+
$cleanNames = $branches | ForEach-Object {
94+
$_.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', ''
8895
}
96+
return Get-HighestNumberFromNames -Names $cleanNames
8997
}
9098
} catch {
91-
# If git command fails, return 0
9299
Write-Verbose "Could not check Git branches: $_"
93100
}
94-
return $highest
101+
return 0
95102
}
96103

97104
function Get-HighestNumberFromRemoteRefs {
@@ -102,18 +109,11 @@ function Get-HighestNumberFromRemoteRefs {
102109
foreach ($remote in $remotes) {
103110
$refs = git ls-remote --heads $remote 2>$null
104111
if ($LASTEXITCODE -eq 0 -and $refs) {
105-
foreach ($line in $refs) {
106-
# Extract branch name from refs/heads/branch-name
107-
if ($line -match 'refs/heads/(.+)$') {
108-
$ref = $matches[1]
109-
if ($ref -match '^(\d{3,})-' -and $ref -notmatch '^\d{8}-\d{6}-') {
110-
[long]$num = 0
111-
if ([long]::TryParse($matches[1], [ref]$num) -and $num -gt $highest) {
112-
$highest = $num
113-
}
114-
}
115-
}
116-
}
112+
$refNames = $refs | ForEach-Object {
113+
if ($_ -match 'refs/heads/(.+)$') { $matches[1] }
114+
} | Where-Object { $_ }
115+
$remoteHighest = Get-HighestNumberFromNames -Names $refNames
116+
if ($remoteHighest -gt $highest) { $highest = $remoteHighest }
117117
}
118118
}
119119
}
@@ -123,21 +123,29 @@ function Get-HighestNumberFromRemoteRefs {
123123
return $highest
124124
}
125125

126+
# Return next available branch number. When SkipFetch is true, queries remotes
127+
# via ls-remote (read-only) instead of fetching.
126128
function Get-NextBranchNumber {
127129
param(
128-
[string]$SpecsDir
130+
[string]$SpecsDir,
131+
[switch]$SkipFetch
129132
)
130133

131-
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
132-
try {
133-
git fetch --all --prune 2>$null | Out-Null
134-
} catch {
135-
# Ignore fetch errors
134+
if ($SkipFetch) {
135+
# Side-effect-free: query remotes via ls-remote
136+
$highestBranch = Get-HighestNumberFromBranches
137+
$highestRemote = Get-HighestNumberFromRemoteRefs
138+
$highestBranch = [Math]::Max($highestBranch, $highestRemote)
139+
} else {
140+
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
141+
try {
142+
git fetch --all --prune 2>$null | Out-Null
143+
} catch {
144+
# Ignore fetch errors
145+
}
146+
$highestBranch = Get-HighestNumberFromBranches
136147
}
137148

138-
# Get highest number from ALL branches (not just matching short name)
139-
$highestBranch = Get-HighestNumberFromBranches
140-
141149
# Get highest number from ALL specs (not just matching short name)
142150
$highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir
143151

@@ -236,16 +244,12 @@ if ($Timestamp) {
236244
} else {
237245
# Determine branch number
238246
if ($Number -eq 0) {
239-
if ($DryRun) {
240-
# Dry-run: query remote refs without fetching (side-effect-free)
241-
$highestBranch = 0
242-
if ($hasGit) {
243-
$highestBranch = Get-HighestNumberFromBranches
244-
$highestRemote = Get-HighestNumberFromRemoteRefs
245-
$highestBranch = [Math]::Max($highestBranch, $highestRemote)
246-
}
247-
$highestSpec = Get-HighestNumberFromSpecs -SpecsDir $specsDir
248-
$Number = [Math]::Max($highestBranch, $highestSpec) + 1
247+
if ($DryRun -and $hasGit) {
248+
# Dry-run: query remotes via ls-remote (side-effect-free, no fetch)
249+
$Number = Get-NextBranchNumber -SpecsDir $specsDir -SkipFetch
250+
} elseif ($DryRun) {
251+
# Dry-run without git: local spec dirs only
252+
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
249253
} elseif ($hasGit) {
250254
# Check existing branches on remotes
251255
$Number = Get-NextBranchNumber -SpecsDir $specsDir

0 commit comments

Comments
 (0)