From b5e2b2e442b89c24498ee7b9472acbeefc5360dc Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 8 May 2026 14:48:27 +0200 Subject: [PATCH 1/5] Add new GitConfig resource --- resources/GitDsc/GitDsc.psd1 | 6 +- resources/GitDsc/GitDsc.psm1 | 221 +++++++++++++ .../GitDsc/SetGitConfigUserEmail.v3.winget | 47 +++ tests/GitDsc/GitDsc.tests.ps1 | 304 +++++++++++++++++- 4 files changed, 571 insertions(+), 7 deletions(-) create mode 100644 samples/DscResources/GitDsc/SetGitConfigUserEmail.v3.winget diff --git a/resources/GitDsc/GitDsc.psd1 b/resources/GitDsc/GitDsc.psd1 index ae763c0b..80e905b4 100644 --- a/resources/GitDsc/GitDsc.psd1 +++ b/resources/GitDsc/GitDsc.psd1 @@ -86,7 +86,7 @@ 'GitRemote', 'GitConfigUserName', 'GitConfigUserEmail', - 'GitConfigFile' + 'GitConfig' ) # List of all modules packaged with this module @@ -101,10 +101,10 @@ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('PSDscResource_GitClone', 'PSDscResource_GitRemote', 'PSDscResource_GitConfigUserName', 'PSDscResource_GitConfigUserEmail', 'PSDscResource_GitConfigFile') + Tags = @('PSDscResource_GitClone', 'PSDscResource_GitRemote', 'PSDscResource_GitConfigUserName', 'PSDscResource_GitConfigUserEmail', 'PSDscResource_GitConfig') # A URL to the license for this module. - LicenseURI= 'https://github.com/microsoft/winget-dsc/blob/main/LICENSE' + LicenseURI = 'https://github.com/microsoft/winget-dsc/blob/main/LICENSE' # A URL to the main website for this project. ProjectUri = 'https://github.com/microsoft/winget-dsc/' diff --git a/resources/GitDsc/GitDsc.psm1 b/resources/GitDsc/GitDsc.psm1 index 59c2c962..968ed1ea 100644 --- a/resources/GitDsc/GitDsc.psm1 +++ b/resources/GitDsc/GitDsc.psm1 @@ -431,6 +431,184 @@ class GitConfigUserEmail { } } +<# + .SYNOPSIS + The `GitConfig` DSC resource is used to manage any Git configuration setting. + + .DESCRIPTION + The `GitConfig` DSC resource sets or removes any `git config` key-value pair at + the specified configuration scope (local, global, system, or worktree). This is a + general-purpose resource that can manage any setting supported by `git config`. + + ## Requirements + + * Target machine must have Git installed. + * For system-level configuration, the resource must be run as an Administrator. + + .PARAMETER Name + The Git configuration key to manage (e.g., `core.longpaths`, `user.name`, `init.defaultBranch`). + This is a key property. + + .PARAMETER ConfigLocation + The Git configuration scope to apply the setting to (`global`, `system`, `local`, `worktree`, + or `none`). This is a key property. + + .PARAMETER Value + The value to assign to the configuration key. Required when `Exist` is `$true`. + + .PARAMETER Exist + Indicates whether the configuration key should exist with the specified value. + Defaults to `$true`. + + .PARAMETER ProjectDirectory + The path to the Git repository. Required for `local` and `worktree` configurations. + + .EXAMPLE + Invoke-DscResource -ModuleName GitDsc -Name GitConfig -Method Set -Property @{ + Name = 'core.longpaths' + Value = 'true' + ConfigLocation = 'global' + } + + This example sets the global Git core.longpaths configuration to true. + + .EXAMPLE + Invoke-DscResource -ModuleName GitDsc -Name GitConfig -Method Set -Property @{ + Name = 'core.autocrlf' + Value = 'input' + ConfigLocation = 'local' + ProjectDirectory = 'C:\repos\myproject' + } + + This example sets the local Git core.autocrlf configuration to 'input'. + + .EXAMPLE + Invoke-DscResource -ModuleName GitDsc -Name GitConfig -Method Set -Property @{ + Name = 'http.proxy' + ConfigLocation = 'global' + Exist = $false + } + + This example removes the global Git http.proxy configuration. +#> +[DSCResource()] +class GitConfig { + [DscProperty(Key)] + [string] $Name + + [DscProperty(Key)] + [ConfigLocation] $ConfigLocation + + [DscProperty()] + [string] $Value + + [DscProperty()] + [bool] $Exist = $true + + [DscProperty()] + [string] $ProjectDirectory + + [GitConfig] Get() { + Assert-Git + + $currentState = [GitConfig]::new() + $currentState.Name = $this.Name + $currentState.ConfigLocation = $this.ConfigLocation + $currentState.Value = $this.Value + $currentState.ProjectDirectory = $this.ProjectDirectory + + Invoke-GitWorkingDirectory -ConfigLocation $this.ConfigLocation -ProjectDirectory $this.ProjectDirectory + + $gitArgs = Get-GitConfigArguments -ConfigLocation $this.ConfigLocation -Tail @($this.Name) + $result = & git @gitArgs 2>&1 + + if ($LASTEXITCODE -eq 0) { + $currentState.Exist = $true + $currentState.Value = ($result | Out-String).Trim() + } else { + $currentState.Exist = $false + } + + return $currentState + } + + [bool] Test() { + $currentState = $this.Get() + + if ($currentState.Exist -ne $this.Exist) { + return $false + } + + if ($this.Exist -and (-not [string]::IsNullOrEmpty($this.Value)) -and ($currentState.Value -ne $this.Value)) { + return $false + } + + return $true + } + + [void] Set() { + if ($this.Test()) { + return + } + + if ($this.ConfigLocation -eq [ConfigLocation]::system) { + Assert-IsAdministrator + } + + Invoke-GitWorkingDirectory -ConfigLocation $this.ConfigLocation -ProjectDirectory $this.ProjectDirectory + + if ($this.Exist) { + if ([string]::IsNullOrEmpty($this.Value)) { + throw 'Value must be specified when Exist is true.' + } + $gitArgs = Get-GitConfigArguments -ConfigLocation $this.ConfigLocation -Tail @($this.Name, $this.Value) + } else { + $gitArgs = Get-GitConfigArguments -ConfigLocation $this.ConfigLocation -Tail @('--unset', $this.Name) + } + + & git @gitArgs + if ($LASTEXITCODE -ne 0) { + throw "Failed to configure git setting '$($this.Name)' at scope '$($this.ConfigLocation)'." + } + } + + [GitConfig[]] Export([ConfigLocation]$ConfigLocation, [string]$ProjectDirectory) { + Assert-Git + + Invoke-GitWorkingDirectory -ConfigLocation $ConfigLocation -ProjectDirectory $ProjectDirectory + + $gitArgs = Get-GitConfigArguments -ConfigLocation $ConfigLocation -Tail @('--list') + $output = & git @gitArgs 2>&1 + + if ($LASTEXITCODE -ne 0 -or $null -eq $output) { + return @() + } + + $results = [System.Collections.Generic.List[GitConfig]]::new() + + foreach ($line in $output) { + # git config --list outputs "key=value"; split only on the first '=' so values containing '=' are preserved + $separatorIndex = $line.IndexOf('=') + if ($separatorIndex -lt 0) { continue } + + $entry = [GitConfig]::new() + $entry.Name = $line.Substring(0, $separatorIndex) + $entry.Value = $line.Substring($separatorIndex + 1) + $entry.ConfigLocation = $ConfigLocation + $entry.ProjectDirectory = $ProjectDirectory + $entry.Exist = $true + + $results.Add($entry) + } + + return $results.ToArray() + } + + [GitConfig[]] Export([ConfigLocation]$ConfigLocation) { + return $this.Export($ConfigLocation, '') + } +} + #endregion DSCResources #region Functions @@ -540,4 +718,47 @@ function Assert-GitUrl { throw "Invalid Git URL: $HttpsUrl. Error: $out" } } + +function Invoke-GitWorkingDirectory { + param ( + [Parameter(Mandatory)] + [ConfigLocation] $ConfigLocation, + + [Parameter()] + [string] $ProjectDirectory + ) + + if ($ConfigLocation -eq [ConfigLocation]::local -or $ConfigLocation -eq [ConfigLocation]::worktree) { + if ([string]::IsNullOrEmpty($ProjectDirectory)) { + throw 'ProjectDirectory must be specified for local and worktree configurations.' + } + if (-not (Test-Path -Path $ProjectDirectory)) { + throw "ProjectDirectory '$ProjectDirectory' does not exist." + } + Set-Location -Path $ProjectDirectory + } +} + +function Get-GitConfigArguments { + param ( + [Parameter(Mandatory)] + [ConfigLocation] $ConfigLocation, + + [Parameter(Mandatory)] + [string[]] $Tail + ) + + $gitArgs = [System.Collections.Generic.List[string]]::new() + $gitArgs.Add('config') + + if ($ConfigLocation -ne [ConfigLocation]::none) { + $gitArgs.Add("--$($ConfigLocation.ToString().ToLower())") + } + + foreach ($item in $Tail) { + $gitArgs.Add($item) + } + + return $gitArgs.ToArray() +} #endregion Functions diff --git a/samples/DscResources/GitDsc/SetGitConfigUserEmail.v3.winget b/samples/DscResources/GitDsc/SetGitConfigUserEmail.v3.winget new file mode 100644 index 00000000..228e7c31 --- /dev/null +++ b/samples/DscResources/GitDsc/SetGitConfigUserEmail.v3.winget @@ -0,0 +1,47 @@ +################################################################################################## +# This configuration will set the global Git user email to a GitHub noreply address. # +# PowerShell module: GitDsc (v1.0.0) # +################################################################################################## + +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: + identifier: dscv3 +resources: + - type: Microsoft.WinGet/Package + name: Git + properties: + id: Git.Git + source: winget + useLatest: true + metadata: + description: Install Git + winget: + securityContext: elevated + - type: Microsoft.DSC.Transitional/RunCommandOnSet + name: GitDsc.Module + properties: + executable: C:\Program Files\PowerShell\7\pwsh.exe + arguments: + "0": -NoProfile + "1": -NoLogo + "2": -Command + "3": >- + if (-not (Get-Module -ListAvailable -Name GitDsc)) + { Install-PSResource -Name GitDsc + -TrustRepository -AcceptLicense } + treatAsArray: true + metadata: + description: Ensure GitDsc module is installed + - type: GitDsc/GitConfig + name: SetGlobalUserEmail + dependsOn: + - Git + - GitDsc.Module + properties: + Name: user.email + Value: 12345678+username@users.noreply.github.com + ConfigLocation: global + metadata: + description: Set the global Git user email to a GitHub noreply address diff --git a/tests/GitDsc/GitDsc.tests.ps1 b/tests/GitDsc/GitDsc.tests.ps1 index 4f5de9b1..2bd160fb 100644 --- a/tests/GitDsc/GitDsc.tests.ps1 +++ b/tests/GitDsc/GitDsc.tests.ps1 @@ -1,4 +1,4 @@ -using module GitDsc +using module GitDsc $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest @@ -19,9 +19,9 @@ BeforeAll { Describe 'List available DSC resources' { It 'Shows DSC Resources' { - $expectedDSCResources = 'GitClone', 'GitRemote', 'GitConfigUserName', 'GitConfigUserEmail' + $expectedDSCResources = 'GitClone', 'GitRemote', 'GitConfigUserName', 'GitConfigUserEmail', 'GitConfig' $availableDSCResources = (Get-DscResource -Module GitDsc).Name - $availableDSCResources.count | Should -Be 4 + $availableDSCResources.count | Should -Be 5 $availableDSCResources | Where-Object { $expectedDSCResources -notcontains $_ } | Should -BeNullOrEmpty -ErrorAction Stop } } @@ -78,6 +78,302 @@ Describe 'GitDsc' { RootDirectory = $env:TEMP } - { (Invoke-DscResource -Name GitClone -ModuleName GitDsc -Method Get -Property $desiredState -ErrorAction Stop) } | Should -Throw + { (Invoke-DscResource -Name GitClone -ModuleName GitDsc -Method Get -Property $desiredState -ErrorAction Stop) } | Should -Throw } } + +Describe 'GitConfig - global scope' { + BeforeAll { + # Capture the original value so we can restore it after the test suite + $script:originalLongPaths = & git config --global core.longpaths 2>$null + } + + AfterAll { + # Restore original state + if ($null -ne $script:originalLongPaths) { + & git config --global core.longpaths $script:originalLongPaths + } else { + & git config --global --unset core.longpaths 2>$null + } + } + + It 'Sets a global git config value' { + $desiredState = @{ + Name = 'core.longpaths' + Value = 'true' + ConfigLocation = 'global' + } + + Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState + + $finalState = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Get -Property $desiredState + $finalState.Name | Should -Be $desiredState.Name + $finalState.Value | Should -Be $desiredState.Value + $finalState.Exist | Should -BeTrue + } + + It 'Reports InDesiredState when value already matches' { + $desiredState = @{ + Name = 'core.longpaths' + Value = 'true' + ConfigLocation = 'global' + } + + # Ensure the value is set first + Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState + + $testResult = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Test -Property $desiredState + $testResult.InDesiredState | Should -BeTrue + } + + It 'Reports not InDesiredState when value differs' { + # Set value to 'false' + & git config --global core.longpaths false + + $desiredState = @{ + Name = 'core.longpaths' + Value = 'true' + ConfigLocation = 'global' + } + + $testResult = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Test -Property $desiredState + $testResult.InDesiredState | Should -BeFalse + } + + It 'Updates a global git config value' { + # Set initial value + & git config --global core.longpaths false + + $desiredState = @{ + Name = 'core.longpaths' + Value = 'true' + ConfigLocation = 'global' + } + + Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState + + $finalState = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Get -Property $desiredState + $finalState.Value | Should -Be 'true' + $finalState.Exist | Should -BeTrue + } + + It 'Removes a global git config value when Exist is false' { + # Ensure the value exists first + & git config --global core.longpaths true + + $desiredState = @{ + Name = 'core.longpaths' + ConfigLocation = 'global' + Exist = $false + } + + Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState + + $finalState = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Get -Property $desiredState + $finalState.Exist | Should -BeFalse + } + + It 'Reports InDesiredState when key is absent and Exist is false' { + # Ensure the key does not exist + & git config --global --unset core.longpaths 2>$null + + $desiredState = @{ + Name = 'core.longpaths' + ConfigLocation = 'global' + Exist = $false + } + + $testResult = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Test -Property $desiredState + $testResult.InDesiredState | Should -BeTrue + } + + It 'Sets init.defaultBranch to main' { + $desiredState = @{ + Name = 'init.defaultBranch' + Value = 'main' + ConfigLocation = 'global' + } + + Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState + + $finalState = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Get -Property $desiredState + $finalState.Name | Should -Be 'init.defaultBranch' + $finalState.Value | Should -Be 'main' + $finalState.Exist | Should -BeTrue + } +} + +Describe 'GitConfig - local scope' { + BeforeAll { + # Create a temporary git repository for local-scope tests + $script:tempRepo = Join-Path ([System.IO.Path]::GetTempPath()) "gitconfig-test-$(New-Guid)" + New-Item -ItemType Directory -Path $script:tempRepo | Out-Null + & git -C $script:tempRepo init + } + + AfterAll { + # Clean up the temporary repository + if (Test-Path $script:tempRepo) { + Remove-Item -Recurse -Force -Path $script:tempRepo + } + } + + It 'Sets a local git config value' { + $desiredState = @{ + Name = 'core.autocrlf' + Value = 'input' + ConfigLocation = 'local' + ProjectDirectory = $script:tempRepo + } + + Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState + + $finalState = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Get -Property $desiredState + $finalState.Name | Should -Be 'core.autocrlf' + $finalState.Value | Should -Be 'input' + $finalState.Exist | Should -BeTrue + } + + It 'Reports InDesiredState for a local git config value' { + $desiredState = @{ + Name = 'core.autocrlf' + Value = 'input' + ConfigLocation = 'local' + ProjectDirectory = $script:tempRepo + } + + # Ensure the value is set first + Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState + + $testResult = Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Test -Property $desiredState + $testResult.InDesiredState | Should -BeTrue + } + + It 'Throws when ProjectDirectory is missing for local scope' { + $desiredState = @{ + Name = 'core.autocrlf' + Value = 'input' + ConfigLocation = 'local' + } + + { Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Get -Property $desiredState } | Should -Throw + } + + It 'Throws when ProjectDirectory does not exist' { + $desiredState = @{ + Name = 'core.autocrlf' + Value = 'input' + ConfigLocation = 'local' + ProjectDirectory = 'C:\this\path\does\not\exist' + } + + { Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Get -Property $desiredState } | Should -Throw + } +} + +Describe 'GitConfig - Export' { + BeforeAll { + # Create a temporary git repository for Export local-scope tests + $script:exportTempRepo = Join-Path ([System.IO.Path]::GetTempPath()) "gitconfig-export-test-$(New-Guid)" + New-Item -ItemType Directory -Path $script:exportTempRepo | Out-Null + & git -C $script:exportTempRepo init + & git -C $script:exportTempRepo config core.autocrlf input + & git -C $script:exportTempRepo config core.filemode false + + # Ensure at least one known global setting exists for the export tests + & git config --global core.longpaths true + } + + AfterAll { + & git config --global --unset core.longpaths 2>$null + + if (Test-Path $script:exportTempRepo) { + Remove-Item -Recurse -Force -Path $script:exportTempRepo + } + } + + It 'Exports all global git config entries as GitConfig objects' { + $instance = [GitConfig]::new() + $results = $instance.Export([ConfigLocation]::global) + + $results | Should -Not -BeNullOrEmpty + $results | ForEach-Object { + $_ | Should -BeOfType ([GitConfig]) + $_.ConfigLocation | Should -Be ([ConfigLocation]::global) + $_.Exist | Should -BeTrue + $_.Name | Should -Not -BeNullOrEmpty + } + } + + It 'Export includes a known global setting' { + $instance = [GitConfig]::new() + $results = $instance.Export([ConfigLocation]::global) + + $match = $results | Where-Object { $_.Name -eq 'core.longpaths' } + $match | Should -Not -BeNullOrEmpty + $match.Value | Should -Be 'true' + } + + It 'Exports local git config entries for a repository' { + $instance = [GitConfig]::new() + $results = $instance.Export([ConfigLocation]::local, $script:exportTempRepo) + + $results | Should -Not -BeNullOrEmpty + $results | ForEach-Object { + $_ | Should -BeOfType ([GitConfig]) + $_.ConfigLocation | Should -Be ([ConfigLocation]::local) + $_.ProjectDirectory | Should -Be $script:exportTempRepo + $_.Exist | Should -BeTrue + } + } + + It 'Export includes known local settings' { + $instance = [GitConfig]::new() + $results = $instance.Export([ConfigLocation]::local, $script:exportTempRepo) + + $autocrlfEntry = $results | Where-Object { $_.Name -eq 'core.autocrlf' } + $autocrlfEntry | Should -Not -BeNullOrEmpty + $autocrlfEntry.Value | Should -Be 'input' + + $filemodeEntry = $results | Where-Object { $_.Name -eq 'core.filemode' } + $filemodeEntry | Should -Not -BeNullOrEmpty + $filemodeEntry.Value | Should -Be 'false' + } + + It 'Single-argument Export overload returns the same results as the two-argument overload for global scope' { + $instance = [GitConfig]::new() + $twoArg = $instance.Export([ConfigLocation]::global, '') + $oneArg = $instance.Export([ConfigLocation]::global) + + $oneArg.Count | Should -Be $twoArg.Count + } + + It 'Exports all settings when ConfigLocation is none' { + $instance = [GitConfig]::new() + $results = $instance.Export([ConfigLocation]::none) + + # none scope lists settings from all scopes - should be non-empty + $results | Should -Not -BeNullOrEmpty + $results | ForEach-Object { + $_ | Should -BeOfType ([GitConfig]) + $_.Exist | Should -BeTrue + } + } +} + +Describe 'GitConfig - validation' { + It 'Throws when Value is not specified and Exist is true' { + $desiredState = @{ + Name = 'core.longpaths' + ConfigLocation = 'global' + Exist = $true + } + + { Invoke-DscResource -Name GitConfig -ModuleName GitDsc -Method Set -Property $desiredState } | Should -Throw + } +} + +AfterAll { + # Clean up init.defaultBranch if we set it during tests + & git config --global --unset init.defaultBranch 2>$null +} From 8aa1b3a8801ce5e5460c61125b44c4320668677c Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 8 May 2026 14:56:14 +0200 Subject: [PATCH 2/5] Fix spelling --- .github/actions/spelling/expect/generic_terms.txt | 3 +++ resources/GitDsc/GitDsc.psm1 | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/expect/generic_terms.txt b/.github/actions/spelling/expect/generic_terms.txt index 9cc94fa6..5e789093 100644 --- a/.github/actions/spelling/expect/generic_terms.txt +++ b/.github/actions/spelling/expect/generic_terms.txt @@ -21,3 +21,6 @@ usr versioning VGpu vse +longpaths +filemode + diff --git a/resources/GitDsc/GitDsc.psm1 b/resources/GitDsc/GitDsc.psm1 index 968ed1ea..c751a850 100644 --- a/resources/GitDsc/GitDsc.psm1 +++ b/resources/GitDsc/GitDsc.psm1 @@ -477,7 +477,7 @@ class GitConfigUserEmail { Name = 'core.autocrlf' Value = 'input' ConfigLocation = 'local' - ProjectDirectory = 'C:\repos\myproject' + ProjectDirectory = 'C:\repos\MyProject' } This example sets the local Git core.autocrlf configuration to 'input'. From 9eaf8d28bec415fa772b34c8af75fa04211a1643 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Mon, 18 May 2026 16:55:49 +0200 Subject: [PATCH 3/5] Resolve remark --- resources/GitDsc/GitDsc.psm1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/GitDsc/GitDsc.psm1 b/resources/GitDsc/GitDsc.psm1 index c751a850..0b09e559 100644 --- a/resources/GitDsc/GitDsc.psm1 +++ b/resources/GitDsc/GitDsc.psm1 @@ -511,6 +511,10 @@ class GitConfig { [GitConfig] Get() { Assert-Git + if ($this.Exist -and [string]::IsNullOrEmpty($this.Value)) { + throw 'Value must be specified when Exist is true.' + } + $currentState = [GitConfig]::new() $currentState.Name = $this.Name $currentState.ConfigLocation = $this.ConfigLocation @@ -558,9 +562,6 @@ class GitConfig { Invoke-GitWorkingDirectory -ConfigLocation $this.ConfigLocation -ProjectDirectory $this.ProjectDirectory if ($this.Exist) { - if ([string]::IsNullOrEmpty($this.Value)) { - throw 'Value must be specified when Exist is true.' - } $gitArgs = Get-GitConfigArguments -ConfigLocation $this.ConfigLocation -Tail @($this.Name, $this.Value) } else { $gitArgs = Get-GitConfigArguments -ConfigLocation $this.ConfigLocation -Tail @('--unset', $this.Name) From dd2d42bc0ed3f31672804903b35c63fe0e6e7ade Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Mon, 18 May 2026 16:58:18 +0200 Subject: [PATCH 4/5] Fix using -z --- resources/GitDsc/GitDsc.psm1 | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/resources/GitDsc/GitDsc.psm1 b/resources/GitDsc/GitDsc.psm1 index 0b09e559..8b50094b 100644 --- a/resources/GitDsc/GitDsc.psm1 +++ b/resources/GitDsc/GitDsc.psm1 @@ -578,28 +578,32 @@ class GitConfig { Invoke-GitWorkingDirectory -ConfigLocation $ConfigLocation -ProjectDirectory $ProjectDirectory - $gitArgs = Get-GitConfigArguments -ConfigLocation $ConfigLocation -Tail @('--list') - $output = & git @gitArgs 2>&1 + # Use --null (-z) so each entry is NUL-terminated and key/value are separated by a newline. + $gitArgs = Get-GitConfigArguments -ConfigLocation $ConfigLocation -Tail @('--list', '--null') + $rawLines = & git @gitArgs 2>&1 - if ($LASTEXITCODE -ne 0 -or $null -eq $output) { + if ($LASTEXITCODE -ne 0 -or $null -eq $rawLines) { return @() } + $rawOutput = $rawLines -join "`n" + $entries = $rawOutput.Split([char]0, [System.StringSplitOptions]::RemoveEmptyEntries) + $results = [System.Collections.Generic.List[GitConfig]]::new() - foreach ($line in $output) { - # git config --list outputs "key=value"; split only on the first '=' so values containing '=' are preserved - $separatorIndex = $line.IndexOf('=') + foreach ($entry in $entries) { + # Each entry is "key\nvalue"; split on the first newline only + $separatorIndex = $entry.IndexOf("`n") if ($separatorIndex -lt 0) { continue } - $entry = [GitConfig]::new() - $entry.Name = $line.Substring(0, $separatorIndex) - $entry.Value = $line.Substring($separatorIndex + 1) - $entry.ConfigLocation = $ConfigLocation - $entry.ProjectDirectory = $ProjectDirectory - $entry.Exist = $true + $gitEntry = [GitConfig]::new() + $gitEntry.Name = $entry.Substring(0, $separatorIndex) + $gitEntry.Value = $entry.Substring($separatorIndex + 1) + $gitEntry.ConfigLocation = $ConfigLocation + $gitEntry.ProjectDirectory = $ProjectDirectory + $gitEntry.Exist = $true - $results.Add($entry) + $results.Add($gitEntry) } return $results.ToArray() From 03077830babfcac39212f57a93768fd28c81eea0 Mon Sep 17 00:00:00 2001 From: GijsR <26114636+Gijsreyn@users.noreply.github.com> Date: Tue, 19 May 2026 11:47:35 +0200 Subject: [PATCH 5/5] Dont whitelist spelling mistake instead fix --- resources/GitDsc/GitDsc.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/GitDsc/GitDsc.psm1 b/resources/GitDsc/GitDsc.psm1 index 8b50094b..db88a73c 100644 --- a/resources/GitDsc/GitDsc.psm1 +++ b/resources/GitDsc/GitDsc.psm1 @@ -592,7 +592,7 @@ class GitConfig { $results = [System.Collections.Generic.List[GitConfig]]::new() foreach ($entry in $entries) { - # Each entry is "key\nvalue"; split on the first newline only + # Each entry is "key\newValue"; split on the first newline only $separatorIndex = $entry.IndexOf("`n") if ($separatorIndex -lt 0) { continue }