Skip to content
Merged
106 changes: 1 addition & 105 deletions scripts/linting/Validate-MarkdownFrontmatter.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ function Test-FrontmatterValidation {
# Handle ChangedFilesOnly mode
if ($ChangedFilesOnly) {
Write-Host "🔍 Detecting changed markdown files from git diff..." -ForegroundColor Cyan
$Files = @(Get-ChangedMarkdownFileGroup -BaseBranch $BaseBranch)
$Files = @(Get-ChangedFilesFromGit -BaseBranch $BaseBranch -FileExtensions @('*.md'))
if (@($Files).Count -eq 0) {
Write-Host "No changed markdown files found - validation complete" -ForegroundColor Green
# Return empty summary with TotalFiles=0 to accurately represent no files validated
Expand Down Expand Up @@ -833,110 +833,6 @@ All frontmatter fields are valid and properly formatted. Great job! 🎉
return $summary
}

function Get-ChangedMarkdownFileGroup {
<#
.SYNOPSIS
Retrieves changed markdown files from git diff comparison.

.DESCRIPTION
Uses git diff to identify markdown files that have changed between the current
HEAD and a base branch. Implements a fallback strategy when standard comparison
methods fail:

1. First attempts: git merge-base comparison with specified base branch
2. Fallback 1: Comparison with HEAD~1 (previous commit)
3. Fallback 2: Staged and unstaged files against HEAD

.PARAMETER BaseBranch
Git reference for the base branch to compare against. Defaults to 'origin/main'.
Can be any valid git ref (branch name, tag, commit SHA).

.PARAMETER FallbackStrategy
Controls fallback behavior when primary comparison fails.
- 'Auto' (default): Tries all fallback strategies automatically
- 'HeadOnly': Only uses HEAD~1 fallback
- 'None': No fallback, returns empty on failure

.INPUTS
None. Does not accept pipeline input.

.OUTPUTS
[string[]] Array of relative file paths for changed markdown files.
Returns empty array if no changes detected or git operations fail.

.EXAMPLE
$changedFiles = Get-ChangedMarkdownFileGroup
# Returns markdown files changed compared to origin/main

.EXAMPLE
$changedFiles = Get-ChangedMarkdownFileGroup -BaseBranch 'origin/develop'
# Returns markdown files changed compared to develop branch

.EXAMPLE
$changedFiles = Get-ChangedMarkdownFileGroup -FallbackStrategy 'None'
# Returns empty array if merge-base comparison fails

.NOTES
Requires git to be available in PATH. Files must exist on disk to be included
in the result (deleted files are excluded).
#>
[CmdletBinding()]
[OutputType([string[]])]
param(
[Parameter(Mandatory = $false, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$BaseBranch = "origin/main",

[Parameter(Mandatory = $false)]
[ValidateSet('Auto', 'HeadOnly', 'None')]
[string]$FallbackStrategy = 'Auto'
)

try {
$changedFiles = git diff --name-only $(git merge-base HEAD $BaseBranch) HEAD 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Verbose "Merge base comparison with '$BaseBranch' failed"

if ($FallbackStrategy -eq 'None') {
Write-Warning "Unable to determine changed files from git (no fallback enabled)"
return @()
}

Write-Verbose "Attempting fallback: HEAD~1 comparison"
$changedFiles = git diff --name-only HEAD~1 HEAD 2>$null

if ($LASTEXITCODE -ne 0 -and $FallbackStrategy -eq 'Auto') {
Write-Verbose "HEAD~1 comparison failed, attempting staged/unstaged files"
$changedFiles = git diff --name-only HEAD 2>$null

if ($LASTEXITCODE -ne 0) {
Write-Warning "Unable to determine changed files from git"
return @()
}
}
elseif ($LASTEXITCODE -ne 0) {
Write-Warning "Unable to determine changed files from git"
return @()
}
}

[string[]]$changedMarkdownFiles = $changedFiles | Where-Object {
-not [string]::IsNullOrEmpty($_) -and
$_ -match '\.md$' -and
(Test-Path $_ -PathType Leaf)
}

Write-Verbose "Found $($changedMarkdownFiles.Count) changed markdown files from git diff"
$changedMarkdownFiles | ForEach-Object { Write-Verbose " Changed: $_" }

return $changedMarkdownFiles
}
catch {
Write-Warning "Error getting changed files from git: $($_.Exception.Message)"
return @()
}
}

#region Main Execution
if ($MyInvocation.InvocationName -ne '.') {
try {
Expand Down
128 changes: 128 additions & 0 deletions scripts/tests/linting/LintingHelpers.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,134 @@ Describe 'Get-ChangedFilesFromGit' {
$result | Should -BeNullOrEmpty
}
}

Context 'Warning and verbose output' {
It 'Emits warning when git diff returns non-zero exit code' {
Mock git {
$global:LASTEXITCODE = 0
return 'abc123def456789'
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'merge-base' }

Mock git {
$global:LASTEXITCODE = 1
return $null
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'diff' }

$output = Get-ChangedFilesFromGit 3>&1
$warnings = @($output | Where-Object { $_ -is [System.Management.Automation.WarningRecord] })
$warnings | Should -Not -BeNullOrEmpty
}

It 'Emits warning when exception occurs' {
Mock git {
throw "Simulated git failure"
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'merge-base' }

$warnings = Get-ChangedFilesFromGit 3>&1 | Where-Object { $_ -is [System.Management.Automation.WarningRecord] }
$warnings | Should -Not -BeNullOrEmpty
}

It 'Emits verbose message when merge-base succeeds' {
Mock git {
$global:LASTEXITCODE = 0
return 'abc123def456789'
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'merge-base' }

Mock git {
$global:LASTEXITCODE = 0
return @('file.ps1')
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'diff' }

Mock Test-Path { return $true } -ModuleName 'LintingHelpers' -ParameterFilter { $PathType -eq 'Leaf' }

$verbose = Get-ChangedFilesFromGit -Verbose 4>&1 | Where-Object { $_ -is [System.Management.Automation.VerboseRecord] }
$verbose | Should -Not -BeNullOrEmpty
}

It 'Emits verbose message when falling back to HEAD~1' {
Mock git {
$global:LASTEXITCODE = 128
return $null
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'merge-base' }

Mock git {
$global:LASTEXITCODE = 0
return 'HEAD~1-sha'
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'rev-parse' }

Mock git {
$global:LASTEXITCODE = 0
return @('file.ps1')
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'diff' }

Mock Test-Path { return $true } -ModuleName 'LintingHelpers' -ParameterFilter { $PathType -eq 'Leaf' }

$verbose = Get-ChangedFilesFromGit -Verbose 4>&1 | Where-Object { $_ -is [System.Management.Automation.VerboseRecord] }
$verbose | Should -Not -BeNullOrEmpty
($verbose | Where-Object { $_.Message -match 'HEAD~1' }) | Should -Not -BeNullOrEmpty
}
}

Context 'Custom BaseBranch parameter' {
BeforeEach {
Mock git {
$global:LASTEXITCODE = 0
return 'abc123def456789'
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'merge-base' }

Mock git {
$global:LASTEXITCODE = 0
return @('file.md')
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'diff' }

Mock Test-Path { return $true } -ModuleName 'LintingHelpers' -ParameterFilter { $PathType -eq 'Leaf' }
}

It 'Passes custom BaseBranch to merge-base' {
Get-ChangedFilesFromGit -BaseBranch 'origin/develop' -FileExtensions @('*.md')
Should -Invoke git -ModuleName 'LintingHelpers' -ParameterFilter {
$args[0] -eq 'merge-base' -and $args -contains 'origin/develop'
}
}

It 'Uses default BaseBranch when not specified' {
Get-ChangedFilesFromGit -FileExtensions @('*.md')
Should -Invoke git -ModuleName 'LintingHelpers' -ParameterFilter {
$args[0] -eq 'merge-base' -and $args -contains 'origin/main'
}
}
}

Context 'Mixed path separators' {
BeforeEach {
Mock git {
$global:LASTEXITCODE = 0
return 'abc123def456789'
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'merge-base' }

Mock git {
$global:LASTEXITCODE = 0
return @('src/docs/readme.md', 'src\tests\test.md', 'docs/guide.md')
} -ModuleName 'LintingHelpers' -ParameterFilter { $args[0] -eq 'diff' }

Mock Test-Path { return $true } -ModuleName 'LintingHelpers' -ParameterFilter { $PathType -eq 'Leaf' }
}

It 'Handles files with forward slashes' {
$result = Get-ChangedFilesFromGit -FileExtensions @('*.md')
$result | Should -Contain 'src/docs/readme.md'
}

It 'Handles files with backslashes' {
$result = Get-ChangedFilesFromGit -FileExtensions @('*.md')
$result | Should -Contain 'src\tests\test.md'
}

It 'Returns correct count with mixed separators' {
$result = Get-ChangedFilesFromGit -FileExtensions @('*.md')
$result.Count | Should -Be 3
}
}
}

#endregion
Expand Down
Loading
Loading