Skip to content

Commit 55afeb4

Browse files
committed
Add branch completion on "Contains" condition
1 parent 3aedffe commit 55afeb4

4 files changed

Lines changed: 151 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ All notable changes to this project are documented in this file.
44

55
The format is based on Keep a Changelog and the project uses Semantic Versioning.
66

7+
## [0.1.5] - 2026-03-04
8+
9+
### Changed
10+
11+
- Branch completion now supports both match strategies:
12+
- priority 1: branch names that start with the typed fragment (`StartsWith`);
13+
- priority 2: branch names that contain the typed fragment anywhere (`Contains`).
14+
- `gsw` and other branch-oriented alias completions now use the same prioritized branch selection, including native-completion delegation paths.
15+
16+
### Added
17+
18+
- Integration test coverage for branch completion ordering to verify that `StartsWith` results are listed before `Contains` results.
19+
720
## [0.1.4] - 2026-02-20
821

922
### Fixed

git-aliases-extra.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@{
22
RootModule = 'git-aliases-extra.psm1'
3-
ModuleVersion = '0.1.4'
3+
ModuleVersion = '0.1.5'
44
GUID = 'a5c2859e-7dce-4853-9db5-8cb7927dbdda'
55
Author = 'PhysShell'
66
CompanyName = ''

git-aliases-extra.psm1

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,76 @@ function Convert-ToPowerShellBranchCompletionText {
7070
return $BranchName
7171
}
7272

73+
function Get-GitBranchCompletionPrefix {
74+
param(
75+
[AllowEmptyString()]
76+
[string]$WordToComplete
77+
)
78+
79+
$prefix = $WordToComplete.Trim("'", '"')
80+
if ($prefix.StartsWith('`')) {
81+
$prefix = $prefix.Substring(1)
82+
}
83+
84+
return $prefix
85+
}
86+
87+
function Get-GitBranchNames {
88+
if (-not (Test-InGitRepo)) {
89+
return @()
90+
}
91+
92+
try {
93+
return @(
94+
git branch -a --format='%(refname:short)' |
95+
ForEach-Object { $_.Trim() } |
96+
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
97+
ForEach-Object { $_ -replace '^remotes/origin/', '' } |
98+
Where-Object { $_ -ne 'HEAD' } |
99+
Sort-Object -Unique
100+
)
101+
} catch {
102+
return @()
103+
}
104+
}
105+
106+
function Select-GitBranchesForCompletion {
107+
param(
108+
[AllowEmptyCollection()]
109+
[string[]]$Branches,
110+
[AllowEmptyString()]
111+
[string]$Prefix
112+
)
113+
114+
if (-not $Branches -or $Branches.Count -eq 0) {
115+
return @()
116+
}
117+
118+
if ([string]::IsNullOrWhiteSpace($Prefix)) {
119+
return @($Branches)
120+
}
121+
122+
$startsWithMatches = @()
123+
$containsMatches = @()
124+
foreach ($entry in $Branches) {
125+
$branchName = [string]$entry
126+
if ([string]::IsNullOrWhiteSpace($branchName)) {
127+
continue
128+
}
129+
130+
if ($branchName.StartsWith($Prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
131+
$startsWithMatches += $branchName
132+
continue
133+
}
134+
135+
if ($branchName.IndexOf($Prefix, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
136+
$containsMatches += $branchName
137+
}
138+
}
139+
140+
return @($startsWithMatches + $containsMatches)
141+
}
142+
73143
function Get-GitLongOptionCompletions {
74144
param(
75145
[Parameter(Mandatory = $true)]
@@ -272,28 +342,11 @@ function Get-GitBranchCompletions {
272342
[string]$WordToComplete
273343
)
274344

275-
if (-not (Test-InGitRepo)) {
276-
return @()
277-
}
278-
279-
$prefix = $WordToComplete.Trim("'", '"')
280-
if ($prefix.StartsWith('`')) {
281-
$prefix = $prefix.Substring(1)
282-
}
283-
284345
try {
285-
$branches = @(
286-
git branch -a --format='%(refname:short)' |
287-
ForEach-Object { $_.Trim() } |
288-
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
289-
ForEach-Object { $_ -replace '^remotes/origin/', '' } |
290-
Where-Object { $_ -ne 'HEAD' } |
291-
Sort-Object -Unique
292-
)
293-
294-
if (-not [string]::IsNullOrWhiteSpace($prefix)) {
295-
$branches = @($branches | Where-Object { $_ -like "$prefix*" })
296-
}
346+
$prefix = Get-GitBranchCompletionPrefix -WordToComplete $WordToComplete
347+
$branches = Select-GitBranchesForCompletion `
348+
-Branches (Get-GitBranchNames) `
349+
-Prefix $prefix
297350

298351
if (-not $branches -or $branches.Count -eq 0) {
299352
return @()
@@ -1091,6 +1144,14 @@ function Register-GitAliasCompletion {
10911144
}
10921145
}
10931146

1147+
if ($primarySubCommand -in @('checkout', 'switch', 'merge', 'rebase', 'branch', 'reset', 'revert') -and
1148+
$wordToComplete -notlike '-*') {
1149+
$branchCompletions = Get-GitBranchCompletions -WordToComplete $wordToComplete
1150+
if ($branchCompletions -and $branchCompletions.Count -gt 0) {
1151+
return $branchCompletions
1152+
}
1153+
}
1154+
10941155
if ($wordToComplete -like '-*') {
10951156
$optionCompletions = Get-GitLongOptionCompletions -SubCommandLine $subCommandLine -WordToComplete $wordToComplete
10961157
if ($optionCompletions -and $optionCompletions.Count -gt 0) {
@@ -1134,16 +1195,9 @@ function Register-GitAliasCompletion {
11341195
# Final fallback when delegated completion is unavailable
11351196
if ($primarySubCommand -in @('checkout', 'switch', 'merge', 'rebase', 'branch', 'reset', 'revert')) {
11361197
try {
1137-
$branches = git branch -a --format='%(refname:short)' |
1138-
ForEach-Object { $_ -replace '^remotes/origin/', '' } |
1139-
Sort-Object -Unique |
1140-
Where-Object { $_ -like "$wordToComplete*" }
1141-
if ($branches) {
1142-
return $branches | ForEach-Object {
1143-
$branchName = $_
1144-
$safeText = Convert-ToPowerShellBranchCompletionText -BranchName $branchName
1145-
[System.Management.Automation.CompletionResult]::new($safeText, $branchName, 'ParameterValue', $branchName)
1146-
}
1198+
$branchCompletions = Get-GitBranchCompletions -WordToComplete $wordToComplete
1199+
if ($branchCompletions -and $branchCompletions.Count -gt 0) {
1200+
return $branchCompletions
11471201
}
11481202
} catch {
11491203
return @()

tests/git-aliases-extra.Integration.Tests.ps1

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,58 @@ Describe 'gsw integration' {
341341
$completionTexts | Should -Contain '--track'
342342
}
343343

344+
It 'prioritizes starts-with matches before contains matches for gsw branch completion' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue)) {
345+
$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("gsw-contains-complete-" + [guid]::NewGuid().Guid)
346+
$repoPath = Join-Path $tempRoot 'repo'
347+
348+
New-Item -ItemType Directory -Path $repoPath -Force | Out-Null
349+
try {
350+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', $repoPath) | Out-Null
351+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'user.email', 'test@example.com') | Out-Null
352+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'user.name', 'Test User') | Out-Null
353+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'commit.gpgsign', 'false') | Out-Null
354+
355+
Set-Content -Path (Join-Path $repoPath 'README.md') -Value 'root' -NoNewline -Encoding ascii
356+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('add', 'README.md') | Out-Null
357+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('commit', '-m', 'init') | Out-Null
358+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('branch', '-M', 'main') | Out-Null
359+
360+
$startsWithBranches = @('br-alpha', 'br-zulu')
361+
$containsBranches = @('feature/br-mid', 'release-abr')
362+
foreach ($branchName in @($startsWithBranches + $containsBranches)) {
363+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('branch', $branchName) | Out-Null
364+
}
365+
366+
Push-Location $repoPath
367+
try {
368+
$line = 'gsw br'
369+
$result = TabExpansion2 -inputScript $line -cursorColumn $line.Length
370+
} finally {
371+
Pop-Location
372+
}
373+
374+
$result.CompletionMatches.Count | Should -BeGreaterThan 0
375+
$completionTexts = @($result.CompletionMatches | Select-Object -ExpandProperty CompletionText)
376+
377+
foreach ($branchName in $startsWithBranches) {
378+
$completionTexts | Should -Contain $branchName
379+
}
380+
foreach ($branchName in $containsBranches) {
381+
$completionTexts | Should -Contain $branchName
382+
}
383+
384+
$startsWithIndexes = @($startsWithBranches | ForEach-Object { [array]::IndexOf($completionTexts, $_) })
385+
$containsIndexes = @($containsBranches | ForEach-Object { [array]::IndexOf($completionTexts, $_) })
386+
387+
(($startsWithIndexes | Measure-Object -Maximum).Maximum) |
388+
Should -BeLessThan (($containsIndexes | Measure-Object -Minimum).Minimum)
389+
} finally {
390+
if (Test-Path $tempRoot) {
391+
Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
392+
}
393+
}
394+
}
395+
344396
It 'completes long options for gco alias from git-aliases module' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue) -or -not $script:HasGcoAlias) {
345397
Push-Location $script:RepoRoot
346398
try {

0 commit comments

Comments
 (0)