diff --git a/.github/workflows/powershell.yml b/.github/workflows/powershell.yml new file mode 100644 index 000000000..ec9658991 --- /dev/null +++ b/.github/workflows/powershell.yml @@ -0,0 +1,41 @@ +name: Windows PowerShell + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: ['windows-latest'] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: install dependencies + shell: powershell + run: | + "Git version: $(git --version)" + "PSVersion: $($PSVersionTable.PSVersion)" + "Host name: $($Host.Name)" + + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + Install-Module Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Scope CurrentUser -Force + "Pester: $(Get-Module Pester | % Version)" + - name: run tests + shell: powershell + run: | + Import-Module Pester -PassThru + $ErrorActionPreference = 'Continue' + $res = Invoke-Pester -Path test -Output Detailed -PassThru -ErrorAction SilentlyContinue + if (!$res -or ($res.FailedCount -gt 0)) { + $Error | Format-List * -Force + exit 1 + } diff --git a/.github/workflows/ci.yml b/.github/workflows/pwsh.yml similarity index 78% rename from .github/workflows/ci.yml rename to .github/workflows/pwsh.yml index f4a67ea31..0cd76f277 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/pwsh.yml @@ -1,6 +1,14 @@ -name: Test Posh-Git +name: PowerShell Core -on: [push, pull_request] +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: test: @@ -20,6 +28,7 @@ jobs: Set-PSRepository -Name PSGallery -InstallationPolicy Trusted Install-Module Pester -MinimumVersion 5.0.0 -MaximumVersion 5.99.99 -Scope CurrentUser -Force + "Pester: $(Get-Module Pester | % Version)" - name: run tests shell: pwsh run: | diff --git a/src/GitUtils.ps1 b/src/GitUtils.ps1 index 6263806c5..3089ab292 100644 --- a/src/GitUtils.ps1 +++ b/src/GitUtils.ps1 @@ -334,20 +334,29 @@ function Get-GitStatus { '^(?[^#])(?.) (?.*?)(?: -> (?.*))?$' { if ($sw) { dbg "Status: $_" $sw } + $path1 = $matches['path1'] + + # Even with core.quotePath=false, paths with spaces are wrapped in "" + # https://github.com/git/git/commit/dbfdc625a5aad10c47e3ffa446d0b92e341a7b44 + # https://github.com/git/git/commit/f3fc4a1b8680c114defd98ce6f2429f8946a5dc1 + if ($path1 -like '"*"') { + $path1 = $path1.Substring(1, $path1.Length - 2) + } + switch ($matches['index']) { - 'A' { $null = $indexAdded.Add($matches['path1']); break } - 'M' { $null = $indexModified.Add($matches['path1']); break } - 'R' { $null = $indexModified.Add($matches['path1']); break } - 'C' { $null = $indexModified.Add($matches['path1']); break } - 'D' { $null = $indexDeleted.Add($matches['path1']); break } - 'U' { $null = $indexUnmerged.Add($matches['path1']); break } + 'A' { $null = $indexAdded.Add($path1); break } + 'M' { $null = $indexModified.Add($path1); break } + 'R' { $null = $indexModified.Add($path1); break } + 'C' { $null = $indexModified.Add($path1); break } + 'D' { $null = $indexDeleted.Add($path1); break } + 'U' { $null = $indexUnmerged.Add($path1); break } } switch ($matches['working']) { - '?' { $null = $filesAdded.Add($matches['path1']); break } - 'A' { $null = $filesAdded.Add($matches['path1']); break } - 'M' { $null = $filesModified.Add($matches['path1']); break } - 'D' { $null = $filesDeleted.Add($matches['path1']); break } - 'U' { $null = $filesUnmerged.Add($matches['path1']); break } + '?' { $null = $filesAdded.Add($path1); break } + 'A' { $null = $filesAdded.Add($path1); break } + 'M' { $null = $filesModified.Add($path1); break } + 'D' { $null = $filesDeleted.Add($path1); break } + 'U' { $null = $filesUnmerged.Add($path1); break } } continue } diff --git a/src/WindowTitle.ps1 b/src/WindowTitle.ps1 index 9fbc00733..4ba862911 100644 --- a/src/WindowTitle.ps1 +++ b/src/WindowTitle.ps1 @@ -6,6 +6,11 @@ function Test-WindowTitleIsWriteable { # Probe $Host.UI.RawUI.WindowTitle to see if it can be set without errors try { $script:OriginalWindowTitle = $Host.UI.RawUI.WindowTitle + if (!$script:OriginalWindowTitle) { + # Set a reasonable title to revert to on uninstall + $Host.UI.RawUI.WindowTitle = 'PowerShell'; + $script:OriginalWindowTitle = $Host.UI.RawUI.WindowTitle + } $newTitle = "${OriginalWindowTitle} " $Host.UI.RawUI.WindowTitle = $newTitle $script:HostSupportsSettingWindowTitle = ($Host.UI.RawUI.WindowTitle -eq $newTitle) diff --git a/test/DefaultPrompt.Tests.ps1 b/test/DefaultPrompt.Tests.ps1 index 9344455d5..c5da41b12 100644 --- a/test/DefaultPrompt.Tests.ps1 +++ b/test/DefaultPrompt.Tests.ps1 @@ -1,6 +1,10 @@ BeforeAll { . $PSScriptRoot\Shared.ps1 + + [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] + $SkipWindowTitleTests = !(& $module Test-WindowTitleIsWriteable) } + Describe 'Default Prompt Tests - NO ANSI' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] @@ -349,9 +353,7 @@ A test/Foo.Tests.ps1 } } -# Don't run these tests on the AppVeyor build - the Windows PowerShell host is RemoteHostImplementation which doesn't -# support setting the Window title. -Describe 'Default Prompt WindowTitle Tests' -Skip:($Host.Name -eq 'RemoteHostImplementation') { +Describe 'Default Prompt WindowTitle Tests' -Skip:$SkipWindowTitleTests { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $homePath = [regex]::Escape((GetHomePath)) @@ -396,7 +398,7 @@ M test/Baz.Tests.ps1 & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle - if (& $module {$IsAdmin}) { + if (& $module { $IsAdmin }) { $title | Should -Match $repoAdminRegex } else { @@ -413,7 +415,7 @@ M test/Baz.Tests.ps1 & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle - if (& $module {$IsAdmin}) { + if (& $module { $IsAdmin }) { $title | Should -Match '^daboss: poshgit == posh-git / master$' } else { @@ -427,7 +429,7 @@ M test/Baz.Tests.ps1 & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle - if (& $module {$IsAdmin}) { + if (& $module { $IsAdmin }) { $title | Should -Match '^daboss: poshgit == posh-git / master$' } else { @@ -468,7 +470,7 @@ M test/Baz.Tests.ps1 Set-Location $Home & $GitPromptScriptBlock 6>&1 $title = $Host.UI.RawUI.WindowTitle - if (& $module {$IsAdmin}) { + if (& $module { $IsAdmin }) { $title | Should -Match $nonRepoAdminRegex } else { @@ -498,7 +500,7 @@ M test/Baz.Tests.ps1 Set-Location $Home & $GitPromptScriptBlock 6>&1 $title = $Host.UI.RawUI.WindowTitle - if (& $module {$IsAdmin}) { + if (& $module { $IsAdmin }) { $title | Should -Match $nonRepoAdminRegex } else { @@ -509,7 +511,7 @@ M test/Baz.Tests.ps1 & $GitPromptScriptBlock 6>&1 Should -Invoke -ModuleName posh-git -CommandName git -Exactly 1 $title = $Host.UI.RawUI.WindowTitle - if (& $module {$IsAdmin}) { + if (& $module { $IsAdmin }) { $title | Should -Match $repoAdminRegex } else { @@ -519,7 +521,7 @@ M test/Baz.Tests.ps1 Set-Location $Home & $GitPromptScriptBlock 6>&1 $title = $Host.UI.RawUI.WindowTitle - if (& $module {$IsAdmin}) { + if (& $module { $IsAdmin }) { $title | Should -Match $nonRepoAdminRegex } else { @@ -531,17 +533,12 @@ M test/Baz.Tests.ps1 Context 'Removing the posh-git module' { It 'Correctly reverts the Window Title back to original state' { Set-Item function:\prompt -Value ([Runspace]::DefaultRunspace.InitialSessionState.Commands['prompt']).Definition + $originalTitle = & $module { $OriginalWindowTitle } + $originalTitle | Should -Not -BeNullOrEmpty + Remove-Module posh-git -Force *>$null $title = $Host.UI.RawUI.WindowTitle - if ($Host.Name -eq 'RemoteHostImplementation') { - $title | Should -eq $originalTitle - } - elseif ($PSVersionTable.PSVersion.Major -lt 6) { - $title | Should -match '^Windows PowerShell|:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe$' - } - else { - $title | Should -match '^(Administrator: )?(Windows )?PowerShell' - } + $title | Should -eq $originalTitle } } } diff --git a/test/GitProxyFunctionExpansion.Tests.ps1 b/test/GitProxyFunctionExpansion.Tests.ps1 index 9b253b17c..d95a8d828 100644 --- a/test/GitProxyFunctionExpansion.Tests.ps1 +++ b/test/GitProxyFunctionExpansion.Tests.ps1 @@ -5,25 +5,25 @@ BeforeAll { Describe 'Proxy Function Expansion Tests' { Context 'Proxy Function Name TabExpansion Tests' { BeforeEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } - if(Test-Path -Path Alias:\igf) { + if (Test-Path -Path Alias:\igf) { Rename-Item -Path Alias:\igf -NewName igfbackup } - New-Alias -Name 'igf' -Value Invoke-GitFunction -Scope 'Script' + New-Alias -Name 'igf' -Value Invoke-GitFunction -Scope 'Global' } AfterEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } - if(Test-Path -Path Function:\Invoke-GitFunctionBackup) { + if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item Function:\Invoke-GitFunctionBackup Invoke-GitFunction } - if(Test-Path -Path Alias:\igf) { + if (Test-Path -Path Alias:\igf) { Remove-Item -Path Alias:\igf } - if(Test-Path -Path Alias:\igfbackup) { + if (Test-Path -Path Alias:\igfbackup) { Rename-Item -Path Alias:\igfbackup -NewName igf } } @@ -62,25 +62,25 @@ Describe 'Proxy Function Expansion Tests' { } Context 'Proxy Function Definition Expansion Tests' { BeforeEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } - if(Test-Path -Path Alias:\igf) { + if (Test-Path -Path Alias:\igf) { Rename-Item -Path Alias:\igf -NewName igfbackup } - New-Alias -Name 'igf' -Value Invoke-GitFunction -Scope 'Script' + New-Alias -Name 'igf' -Value Invoke-GitFunction -Scope 'Global' } AfterEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } - if(Test-Path -Path Function:\Invoke-GitFunctionBackup) { + if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item Function:\Invoke-GitFunctionBackup Invoke-GitFunction } - if(Test-Path -Path Alias:\igf) { + if (Test-Path -Path Alias:\igf) { Remove-Item -Path Alias:\igf } - if(Test-Path -Path Alias:\igfbackup) { + if (Test-Path -Path Alias:\igfbackup) { Rename-Item -Path Alias:\igfbackup -NewName igf } } @@ -167,8 +167,8 @@ Describe 'Proxy Function Expansion Tests' { function global:Invoke-GitFunction { $a = 5; Write-Host $null git ` - checkout ` - $args; Write-Host $null; + checkout ` + $args; Write-Host $null; } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' @@ -188,8 +188,8 @@ Describe 'Proxy Function Expansion Tests' { It 'Expands multiline function' { function global:Invoke-GitFunction { git ` - checkout ` - $args + checkout ` + $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' @@ -198,9 +198,9 @@ Describe 'Proxy Function Expansion Tests' { It 'Expands multiline function that terminates with semicolon on new line' { function global:Invoke-GitFunction { git ` - checkout ` - $args ` - ; + checkout ` + $args ` + ; } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout ' @@ -209,9 +209,9 @@ Describe 'Proxy Function Expansion Tests' { It 'Expands multiline function with short parameter' { function global:Invoke-GitFunction { git ` - checkout ` - -b ` - $args + checkout ` + -b ` + $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout -b ' @@ -220,9 +220,9 @@ Describe 'Proxy Function Expansion Tests' { It 'Expands multiline function with long parameter' { function global:Invoke-GitFunction { git ` - checkout ` - --detach ` - $args + checkout ` + --detach ` + $args } $result = & $module Expand-GitProxyFunction 'Invoke-GitFunction ' $result | Should -Be 'git checkout --detach ' @@ -255,7 +255,7 @@ Describe 'Proxy Function Expansion Tests' { function global:Invoke-GitFunction { $a = 5 git ` - checkout + checkout Write-Host $args } & $module Expand-GitProxyFunction 'Invoke-GitFunction ' | Should -Be 'Invoke-GitFunction ' @@ -275,16 +275,16 @@ Describe 'Proxy Function Expansion Tests' { } Context 'Proxy Function Parameter Replacement Tests' { BeforeEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } function global:Invoke-GitFunction { git checkout $args } } AfterEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } - if(Test-Path -Path Function:\Invoke-GitFunctionBackup) { + if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item Function:\Invoke-GitFunctionBackup Invoke-GitFunction } } @@ -307,15 +307,15 @@ Describe 'Proxy Function Expansion Tests' { } Context 'Proxy Subcommand TabExpansion Tests' { BeforeEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Rename-Item -Path Function:\Invoke-GitFunction -NewName Invoke-GitFunctionBackup } } AfterEach { - if(Test-Path -Path Function:\Invoke-GitFunction) { + if (Test-Path -Path Function:\Invoke-GitFunction) { Remove-Item -Path Function:\Invoke-GitFunction } - if(Test-Path -Path Function:\Invoke-GitFunctionBackup) { + if (Test-Path -Path Function:\Invoke-GitFunctionBackup) { Rename-Item -Path Function:\Invoke-GitFunctionBackup -NewName Invoke-GitFunction } } diff --git a/test/Shared.ps1 b/test/Shared.ps1 index b4960319e..11c2ba62a 100644 --- a/test/Shared.ps1 +++ b/test/Shared.ps1 @@ -17,9 +17,6 @@ $csi = [char]0x1b + "[" [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] $expectedEncoding = if ($PSVersionTable.PSVersion.Major -le 5) { "utf8" } else { "ascii" } -[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] -$originalTitle = $Host.UI.RawUI.WindowTitle - if (!(Get-Variable -Name gitbin -Scope global -ErrorAction SilentlyContinue)) { if (($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows) { # On Windows, we can access the git binary via git.exe @@ -39,8 +36,8 @@ function global:git { # Write-Warning "in global git func with: $cmdline" switch ($cmdline) { '--version' { 'git version 2.16.2.windows.1' } - 'help' { Get-Content $PSScriptRoot\git-help.txt } - default { + 'help' { Get-Content $PSScriptRoot\git-help.txt } + default { $res = Invoke-Expression "&$gitbin $cmdline" $res } @@ -49,7 +46,7 @@ function global:git { # This must global in order to be accessible in posh-git module scope function global:Convert-NativeLineEnding([string]$content, [switch]$SplitLines) { - $tmp = $content -split "`n" | ForEach-Object { $_.TrimEnd("`r")} + $tmp = $content -split "`n" | ForEach-Object { $_.TrimEnd("`r") } if ($SplitLines) { $tmp } @@ -162,4 +159,4 @@ $env:POSHGIT_ENABLE_STRICTMODE = 1 # Force the posh-git prompt to be installed. Could be runnng on dev system where user has customized the prompt. [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] -$module = Import-Module $moduleManifestPath -ArgumentList $true,$false -Force -PassThru +$module = Import-Module $moduleManifestPath -ArgumentList $true, $false -Force -PassThru diff --git a/test/TabExpansion.Tests.ps1 b/test/TabExpansion.Tests.ps1 index 8ccbb23e5..69c6c6593 100644 --- a/test/TabExpansion.Tests.ps1 +++ b/test/TabExpansion.Tests.ps1 @@ -211,27 +211,6 @@ Describe 'TabExpansion Tests' { } } - Context 'Add/Reset/Checkout TabExpansion Tests' { - BeforeEach { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] - $repoPath = NewGitTempRepo - } - AfterEach { - RemoveGitTempRepo $repoPath - } - It 'Tab completes non-ASCII file name' { - &$gitbin config core.quotepath true # Problematic (default) config - - $fileName = "posh$([char]8226)git.txt" - New-Item $fileName -ItemType File - - $gitStatus = & $module Get-GitStatus - - $result = & $module GitTabExpansionInternal 'git add ' $gitStatus - $result | Should -BeExactly $fileName - } - } - Context 'Git Config Alias TabExpansion Tests' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] @@ -291,7 +270,8 @@ Describe 'TabExpansion Tests' { New-Alias ge git.exe -Scope Global } AfterAll { - Remove-Alias g, ge + Remove-Item Alias:/g + Remove-Item Alias:/ge RemoveGitTempRepo $repoPath } It 'Tab completes PowerShell alias specifying git (with no extension)' { @@ -365,5 +345,16 @@ Describe 'TabExpansion Tests' { $result = & $module GitTabExpansionInternal 'git add ' $gitStatus $result | Should -BeExactly "'$filename'" } + It 'Tab completes add file with non-ASCII file name' { + &$gitbin config core.quotepath true # Problematic (default) config + + $fileName = "posh$([char]8226)git.txt" + New-Item $fileName -ItemType File + + $gitStatus = & $module Get-GitStatus + + $result = & $module GitTabExpansionInternal 'git add ' $gitStatus + $result | Should -BeExactly $fileName + } } }