mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
[release/v7.6] Fix merge conflict checker for empty file lists and filter *.cs files (#26556)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Merge Conflict Checker
|
||||
|
||||
This composite GitHub Action checks for Git merge conflict markers in files changed in pull requests.
|
||||
|
||||
## Purpose
|
||||
|
||||
Automatically detects leftover merge conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) in pull request files to prevent them from being merged into the codebase.
|
||||
|
||||
## Usage
|
||||
|
||||
### In a Workflow
|
||||
|
||||
```yaml
|
||||
- name: Check for merge conflict markers
|
||||
uses: "./.github/actions/infrastructure/merge-conflict-checker"
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
merge_conflict_check:
|
||||
name: Check for Merge Conflict Markers
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
permissions:
|
||||
pull-requests: read
|
||||
contents: read
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Check for merge conflict markers
|
||||
uses: "./.github/actions/infrastructure/merge-conflict-checker"
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **File Detection**: Uses GitHub's API to get the list of files changed in the pull request
|
||||
2. **Marker Scanning**: Reads each changed file and searches for the following markers:
|
||||
- `<<<<<<<` (conflict start marker)
|
||||
- `=======` (conflict separator)
|
||||
- `>>>>>>>` (conflict end marker)
|
||||
3. **Result Reporting**:
|
||||
- If markers are found, the action fails and lists all affected files
|
||||
- If no markers are found, the action succeeds
|
||||
|
||||
## Outputs
|
||||
|
||||
- `files-checked`: Number of files that were checked
|
||||
- `conflicts-found`: Number of files containing merge conflict markers
|
||||
|
||||
## Behavior
|
||||
|
||||
- **Event Support**: Only works with `pull_request` events
|
||||
- **File Handling**:
|
||||
- Checks only files that were added, modified, or renamed
|
||||
- Skips deleted files
|
||||
- **Filters out `*.cs` files** (C# files are excluded from merge conflict checking)
|
||||
- Skips binary/unreadable files
|
||||
- Skips directories
|
||||
- **Empty File List**: Gracefully handles cases where no files need checking (e.g., PRs that only delete files)
|
||||
|
||||
## Example Output
|
||||
|
||||
When conflict markers are detected:
|
||||
|
||||
```
|
||||
❌ Merge conflict markers detected in the following files:
|
||||
- src/example.cs
|
||||
Markers found: <<<<<<<, =======, >>>>>>>
|
||||
- README.md
|
||||
Markers found: <<<<<<<, =======, >>>>>>>
|
||||
|
||||
Please resolve these conflicts before merging.
|
||||
```
|
||||
|
||||
When no markers are found:
|
||||
|
||||
```
|
||||
✅ No merge conflict markers found
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
This action is integrated into the `linux-ci.yml` workflow and runs automatically on all pull requests to ensure code quality before merging.
|
||||
@@ -0,0 +1,37 @@
|
||||
name: 'Check for Merge Conflict Markers'
|
||||
description: 'Checks for Git merge conflict markers in changed files for pull requests'
|
||||
author: 'PowerShell Team'
|
||||
|
||||
outputs:
|
||||
files-checked:
|
||||
description: 'Number of files checked for merge conflict markers'
|
||||
value: ${{ steps.check.outputs.files-checked }}
|
||||
conflicts-found:
|
||||
description: 'Number of files with merge conflict markers'
|
||||
value: ${{ steps.check.outputs.conflicts-found }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: "./.github/actions/infrastructure/get-changed-files"
|
||||
|
||||
- name: Check for merge conflict markers
|
||||
id: check
|
||||
shell: pwsh
|
||||
env:
|
||||
CHANGED_FILES_JSON: ${{ steps.changed-files.outputs.files }}
|
||||
run: |
|
||||
# Get changed files from environment variable (secure against injection)
|
||||
$changedFilesJson = $env:CHANGED_FILES_JSON
|
||||
# Ensure we always have an array (ConvertFrom-Json returns null for empty JSON arrays)
|
||||
$changedFiles = @($changedFilesJson | ConvertFrom-Json)
|
||||
|
||||
# Import ci.psm1 and run the check
|
||||
Import-Module "$env:GITHUB_WORKSPACE/tools/ci.psm1" -Force
|
||||
Test-MergeConflictMarker -File $changedFiles -WorkspacePath $env:GITHUB_WORKSPACE
|
||||
|
||||
branding:
|
||||
icon: 'alert-triangle'
|
||||
color: 'red'
|
||||
@@ -0,0 +1,246 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# NOTE: This test file tests the Test-MergeConflictMarker function which detects Git merge conflict markers.
|
||||
# IMPORTANT: Do NOT use here-strings or literal conflict markers (e.g., "<<<<<<<", "=======", ">>>>>>>")
|
||||
# in this file, as they will trigger conflict marker detection in CI pipelines.
|
||||
# Instead, use string multiplication (e.g., '<' * 7) to dynamically generate these markers at runtime.
|
||||
|
||||
Describe "Test-MergeConflictMarker" {
|
||||
BeforeAll {
|
||||
# Import the module
|
||||
Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force
|
||||
|
||||
# Create a temporary test workspace
|
||||
$script:testWorkspace = Join-Path $TestDrive "workspace"
|
||||
New-Item -ItemType Directory -Path $script:testWorkspace -Force | Out-Null
|
||||
|
||||
# Create temporary output files
|
||||
$script:testOutputPath = Join-Path $TestDrive "outputs.txt"
|
||||
$script:testSummaryPath = Join-Path $TestDrive "summary.md"
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
# Clean up test files after each test
|
||||
if (Test-Path $script:testWorkspace) {
|
||||
Get-ChildItem $script:testWorkspace -File -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Remove-Item $script:testOutputPath -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $script:testSummaryPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Context "When no files are provided" {
|
||||
It "Should handle empty file array gracefully" {
|
||||
# The function now accepts empty arrays to handle cases like delete-only PRs
|
||||
$emptyArray = @()
|
||||
Test-MergeConflictMarker -File $emptyArray -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "files-checked=0"
|
||||
$outputs | Should -Contain "conflicts-found=0"
|
||||
|
||||
$summary = Get-Content $script:testSummaryPath -Raw
|
||||
$summary | Should -Match "No Files to Check"
|
||||
}
|
||||
}
|
||||
|
||||
Context "When files have no conflicts" {
|
||||
It "Should pass for clean files" {
|
||||
$testFile = Join-Path $script:testWorkspace "clean.txt"
|
||||
"This is a clean file" | Out-File $testFile -Encoding utf8
|
||||
|
||||
Test-MergeConflictMarker -File @("clean.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "files-checked=1"
|
||||
$outputs | Should -Contain "conflicts-found=0"
|
||||
|
||||
$summary = Get-Content $script:testSummaryPath -Raw
|
||||
$summary | Should -Match "No Conflicts Found"
|
||||
}
|
||||
}
|
||||
|
||||
Context "When files have conflict markers" {
|
||||
It "Should detect <<<<<<< marker" {
|
||||
$testFile = Join-Path $script:testWorkspace "conflict1.txt"
|
||||
"Some content`n" + ('<' * 7) + " HEAD`nConflicting content" | Out-File $testFile -Encoding utf8
|
||||
|
||||
{ Test-MergeConflictMarker -File @("conflict1.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "files-checked=1"
|
||||
$outputs | Should -Contain "conflicts-found=1"
|
||||
}
|
||||
|
||||
It "Should detect ======= marker" {
|
||||
$testFile = Join-Path $script:testWorkspace "conflict2.txt"
|
||||
"Some content`n" + ('=' * 7) + "`nMore content" | Out-File $testFile -Encoding utf8
|
||||
|
||||
{ Test-MergeConflictMarker -File @("conflict2.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw
|
||||
}
|
||||
|
||||
It "Should detect >>>>>>> marker" {
|
||||
$testFile = Join-Path $script:testWorkspace "conflict3.txt"
|
||||
"Some content`n" + ('>' * 7) + " branch-name`nMore content" | Out-File $testFile -Encoding utf8
|
||||
|
||||
{ Test-MergeConflictMarker -File @("conflict3.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw
|
||||
}
|
||||
|
||||
It "Should detect multiple markers in one file" {
|
||||
$testFile = Join-Path $script:testWorkspace "conflict4.txt"
|
||||
$content = "Some content`n" + ('<' * 7) + " HEAD`nContent A`n" + ('=' * 7) + "`nContent B`n" + ('>' * 7) + " branch`nMore content"
|
||||
$content | Out-File $testFile -Encoding utf8
|
||||
|
||||
{ Test-MergeConflictMarker -File @("conflict4.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw
|
||||
|
||||
$summary = Get-Content $script:testSummaryPath -Raw
|
||||
$summary | Should -Match "Conflicts Detected"
|
||||
$summary | Should -Match "conflict4.txt"
|
||||
}
|
||||
|
||||
It "Should detect conflicts in multiple files" {
|
||||
$testFile1 = Join-Path $script:testWorkspace "conflict5.txt"
|
||||
('<' * 7) + " HEAD" | Out-File $testFile1 -Encoding utf8
|
||||
|
||||
$testFile2 = Join-Path $script:testWorkspace "conflict6.txt"
|
||||
('=' * 7) | Out-File $testFile2 -Encoding utf8
|
||||
|
||||
{ Test-MergeConflictMarker -File @("conflict5.txt", "conflict6.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "files-checked=2"
|
||||
$outputs | Should -Contain "conflicts-found=2"
|
||||
}
|
||||
}
|
||||
|
||||
Context "When markers are not at line start" {
|
||||
It "Should not detect markers in middle of line" {
|
||||
$testFile = Join-Path $script:testWorkspace "notconflict.txt"
|
||||
"This line has <<<<<<< in the middle" | Out-File $testFile -Encoding utf8
|
||||
|
||||
Test-MergeConflictMarker -File @("notconflict.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "conflicts-found=0"
|
||||
}
|
||||
|
||||
It "Should not detect markers with wrong number of characters" {
|
||||
$testFile = Join-Path $script:testWorkspace "wrongcount.txt"
|
||||
('<' * 6) + " Only 6`n" + ('<' * 8) + " 8 characters" | Out-File $testFile -Encoding utf8
|
||||
|
||||
Test-MergeConflictMarker -File @("wrongcount.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "conflicts-found=0"
|
||||
}
|
||||
}
|
||||
|
||||
Context "When handling special file scenarios" {
|
||||
It "Should skip non-existent files" {
|
||||
Test-MergeConflictMarker -File @("nonexistent.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "files-checked=0"
|
||||
}
|
||||
|
||||
It "Should handle absolute paths" {
|
||||
$testFile = Join-Path $script:testWorkspace "absolute.txt"
|
||||
"Clean content" | Out-File $testFile -Encoding utf8
|
||||
|
||||
Test-MergeConflictMarker -File @($testFile) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "conflicts-found=0"
|
||||
}
|
||||
|
||||
It "Should handle mixed relative and absolute paths" {
|
||||
$testFile1 = Join-Path $script:testWorkspace "relative.txt"
|
||||
"Clean" | Out-File $testFile1 -Encoding utf8
|
||||
|
||||
$testFile2 = Join-Path $script:testWorkspace "absolute.txt"
|
||||
"Clean" | Out-File $testFile2 -Encoding utf8
|
||||
|
||||
Test-MergeConflictMarker -File @("relative.txt", $testFile2) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Should -Contain "files-checked=2"
|
||||
$outputs | Should -Contain "conflicts-found=0"
|
||||
}
|
||||
}
|
||||
|
||||
Context "When summary and output generation" {
|
||||
It "Should generate proper GitHub Actions outputs format" {
|
||||
$testFile = Join-Path $script:testWorkspace "test.txt"
|
||||
"Clean file" | Out-File $testFile -Encoding utf8
|
||||
|
||||
Test-MergeConflictMarker -File @("test.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath
|
||||
|
||||
$outputs = Get-Content $script:testOutputPath
|
||||
$outputs | Where-Object {$_ -match "^files-checked=\d+$"} | Should -Not -BeNullOrEmpty
|
||||
$outputs | Where-Object {$_ -match "^conflicts-found=\d+$"} | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should generate markdown summary with conflict details" {
|
||||
$testFile = Join-Path $script:testWorkspace "marked.txt"
|
||||
$content = "Line 1`n" + ('<' * 7) + " HEAD`nLine 3`n" + ('=' * 7) + "`nLine 5"
|
||||
$content | Out-File $testFile -Encoding utf8
|
||||
|
||||
{ Test-MergeConflictMarker -File @("marked.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw
|
||||
|
||||
$summary = Get-Content $script:testSummaryPath -Raw
|
||||
$summary | Should -Match "# Merge Conflict Marker Check Results"
|
||||
$summary | Should -Match "marked.txt"
|
||||
$summary | Should -Match "\| Line \| Marker \|"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Install-CIPester" {
|
||||
BeforeAll {
|
||||
# Import the module
|
||||
Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force
|
||||
}
|
||||
|
||||
Context "When checking function exists" {
|
||||
It "Should export Install-CIPester function" {
|
||||
$function = Get-Command Install-CIPester -ErrorAction SilentlyContinue
|
||||
$function | Should -Not -BeNullOrEmpty
|
||||
$function.ModuleName | Should -Be 'ci'
|
||||
}
|
||||
|
||||
It "Should have expected parameters" {
|
||||
$function = Get-Command Install-CIPester
|
||||
$function.Parameters.Keys | Should -Contain 'MinimumVersion'
|
||||
$function.Parameters.Keys | Should -Contain 'MaximumVersion'
|
||||
$function.Parameters.Keys | Should -Contain 'Force'
|
||||
}
|
||||
|
||||
It "Should accept version parameters" {
|
||||
$function = Get-Command Install-CIPester
|
||||
$function.Parameters['MinimumVersion'].ParameterType.Name | Should -Be 'String'
|
||||
$function.Parameters['MaximumVersion'].ParameterType.Name | Should -Be 'String'
|
||||
$function.Parameters['Force'].ParameterType.Name | Should -Be 'SwitchParameter'
|
||||
}
|
||||
}
|
||||
|
||||
Context "When validating real execution" {
|
||||
# These tests only run in CI where we can safely install/test Pester
|
||||
|
||||
It "Should successfully run without errors when Pester exists" {
|
||||
if (!$env:CI) {
|
||||
Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester"
|
||||
}
|
||||
|
||||
{ Install-CIPester -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Should accept custom version parameters" {
|
||||
if (!$env:CI) {
|
||||
Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester"
|
||||
}
|
||||
|
||||
{ Install-CIPester -MinimumVersion '4.0.0' -MaximumVersion '5.99.99' -ErrorAction Stop } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+223
-1
@@ -972,8 +972,230 @@ function Invoke-InitializeContainerStage {
|
||||
Write-Host "##vso[build.updatebuildnumber]PR-${env:SYSTEM_PULLREQUEST_PULLREQUESTNUMBER}-$($selectedImage.JobName)-$((get-date).ToString("yyyyMMddhhmmss"))"
|
||||
} else {
|
||||
Write-Host "##vso[build.updatebuildnumber]${env:BUILD_SOURCEBRANCHNAME}-$($selectedImage.JobName)-${env:BUILD_SOURCEVERSION}-$((get-date).ToString("yyyyMMddhhmmss"))"
|
||||
|
||||
# Cannot do this for a PR
|
||||
Write-Host "##vso[build.addbuildtag]$($selectedImage.JobName)"
|
||||
}
|
||||
}
|
||||
|
||||
Function Test-MergeConflictMarker
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Checks files for Git merge conflict markers and outputs results for GitHub Actions.
|
||||
.DESCRIPTION
|
||||
Scans the specified files for Git merge conflict markers (<<<<<<<, =======, >>>>>>>)
|
||||
and generates console output, GitHub Actions outputs, and job summary.
|
||||
Designed for use in GitHub Actions workflows.
|
||||
.PARAMETER File
|
||||
Array of file paths (relative or absolute) to check for merge conflict markers.
|
||||
.PARAMETER WorkspacePath
|
||||
Base workspace path for resolving relative paths. Defaults to current directory.
|
||||
.PARAMETER OutputPath
|
||||
Path to write GitHub Actions outputs. Defaults to $env:GITHUB_OUTPUT.
|
||||
.PARAMETER SummaryPath
|
||||
Path to write GitHub Actions job summary. Defaults to $env:GITHUB_STEP_SUMMARY.
|
||||
.EXAMPLE
|
||||
Test-MergeConflictMarker -File @('file1.txt', 'file2.cs') -WorkspacePath $env:GITHUB_WORKSPACE
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter()]
|
||||
[AllowEmptyCollection()]
|
||||
[string[]] $File = @(),
|
||||
|
||||
[Parameter()]
|
||||
[string] $WorkspacePath = $PWD,
|
||||
|
||||
[Parameter()]
|
||||
[string] $OutputPath = $env:GITHUB_OUTPUT,
|
||||
|
||||
[Parameter()]
|
||||
[string] $SummaryPath = $env:GITHUB_STEP_SUMMARY
|
||||
)
|
||||
|
||||
Write-Host "Starting merge conflict marker check..." -ForegroundColor Cyan
|
||||
|
||||
# Helper function to write outputs when no files to check
|
||||
function Write-NoFilesOutput {
|
||||
param(
|
||||
[string]$Message,
|
||||
[string]$OutputPath,
|
||||
[string]$SummaryPath
|
||||
)
|
||||
|
||||
# Output results to GitHub Actions
|
||||
if ($OutputPath) {
|
||||
"files-checked=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8
|
||||
"conflicts-found=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8
|
||||
}
|
||||
|
||||
# Create GitHub Actions job summary
|
||||
if ($SummaryPath) {
|
||||
$summaryContent = @"
|
||||
# Merge Conflict Marker Check Results
|
||||
|
||||
## Summary
|
||||
- **Files Checked:** 0
|
||||
- **Files with Conflicts:** 0
|
||||
|
||||
## ℹ️ No Files to Check
|
||||
|
||||
$Message
|
||||
|
||||
"@
|
||||
$summaryContent | Out-File -FilePath $SummaryPath -Encoding utf8
|
||||
}
|
||||
}
|
||||
|
||||
# Handle empty file list (e.g., when PR only deletes files)
|
||||
if ($File.Count -eq 0) {
|
||||
Write-Host "No files to check (empty file list)" -ForegroundColor Yellow
|
||||
Write-NoFilesOutput -Message "No files were provided for checking (this can happen when a PR only deletes files)." -OutputPath $OutputPath -SummaryPath $SummaryPath
|
||||
return
|
||||
}
|
||||
|
||||
# Filter out *.cs files from merge conflict checking
|
||||
$filesToCheck = @($File | Where-Object { $_ -notlike "*.cs" })
|
||||
$filteredCount = $File.Count - $filesToCheck.Count
|
||||
|
||||
if ($filteredCount -gt 0) {
|
||||
Write-Host "Filtered out $filteredCount *.cs file(s) from merge conflict checking" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if ($filesToCheck.Count -eq 0) {
|
||||
Write-Host "No files to check after filtering (all files were *.cs)" -ForegroundColor Yellow
|
||||
Write-NoFilesOutput -Message "All $filteredCount file(s) were filtered out (*.cs files are excluded from merge conflict checking)." -OutputPath $OutputPath -SummaryPath $SummaryPath
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Checking $($filesToCheck.Count) changed files for merge conflict markers" -ForegroundColor Cyan
|
||||
|
||||
# Convert relative paths to absolute paths for processing
|
||||
$absolutePaths = $filesToCheck | ForEach-Object {
|
||||
if ([System.IO.Path]::IsPathRooted($_)) {
|
||||
$_
|
||||
} else {
|
||||
Join-Path $WorkspacePath $_
|
||||
}
|
||||
}
|
||||
|
||||
$filesWithConflicts = @()
|
||||
$filesChecked = 0
|
||||
|
||||
foreach ($filePath in $absolutePaths) {
|
||||
# Check if file exists (might be deleted)
|
||||
if (-not (Test-Path $filePath)) {
|
||||
Write-Verbose " Skipping deleted file: $filePath"
|
||||
continue
|
||||
}
|
||||
|
||||
# Skip binary files and directories
|
||||
if ((Get-Item $filePath) -is [System.IO.DirectoryInfo]) {
|
||||
continue
|
||||
}
|
||||
|
||||
$filesChecked++
|
||||
|
||||
# Get relative path for display
|
||||
$relativePath = if ($WorkspacePath -and $filePath.StartsWith($WorkspacePath)) {
|
||||
$filePath.Substring($WorkspacePath.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
|
||||
} else {
|
||||
$filePath
|
||||
}
|
||||
|
||||
Write-Host " Checking: $relativePath" -ForegroundColor Gray
|
||||
|
||||
# Search for conflict markers using Select-String
|
||||
try {
|
||||
# Git conflict markers are 7 characters followed by a space or end of line
|
||||
# Regex pattern breakdown:
|
||||
# ^ - Matches the start of a line
|
||||
# (<{7}|={7}|>{7}) - Matches exactly 7 consecutive '<', '=', or '>' characters (Git conflict markers)
|
||||
# (\s|$) - Ensures the marker is followed by whitespace or end of line
|
||||
$pattern = '^(<{7}|={7}|>{7})(\s|$)'
|
||||
$matchedLines = Select-String -Path $filePath -Pattern $pattern -AllMatches -ErrorAction Stop
|
||||
|
||||
if ($matchedLines) {
|
||||
# Collect marker details with line numbers (Select-String provides LineNumber automatically)
|
||||
$markerDetails = @()
|
||||
|
||||
foreach ($match in $matchedLines) {
|
||||
$markerDetails += [PSCustomObject]@{
|
||||
Marker = $match.Matches[0].Groups[1].Value
|
||||
Line = $match.LineNumber
|
||||
}
|
||||
}
|
||||
|
||||
$filesWithConflicts += [PSCustomObject]@{
|
||||
File = $relativePath
|
||||
MarkerDetails = $markerDetails
|
||||
}
|
||||
|
||||
Write-Host " ❌ CONFLICT MARKERS FOUND in $relativePath" -ForegroundColor Red
|
||||
foreach ($detail in $markerDetails) {
|
||||
Write-Host " Line $($detail.Line): $($detail.Marker)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# Skip files that can't be read (likely binary)
|
||||
Write-Verbose " Skipping unreadable file: $relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
# Output results to GitHub Actions
|
||||
if ($OutputPath) {
|
||||
"files-checked=$filesChecked" | Out-File -FilePath $OutputPath -Append -Encoding utf8
|
||||
"conflicts-found=$($filesWithConflicts.Count)" | Out-File -FilePath $OutputPath -Append -Encoding utf8
|
||||
}
|
||||
|
||||
Write-Host "`nSummary:" -ForegroundColor Cyan
|
||||
Write-Host " Files checked: $filesChecked" -ForegroundColor Cyan
|
||||
Write-Host " Files with conflicts: $($filesWithConflicts.Count)" -ForegroundColor Cyan
|
||||
|
||||
# Create GitHub Actions job summary
|
||||
if ($SummaryPath) {
|
||||
$summaryContent = @"
|
||||
# Merge Conflict Marker Check Results
|
||||
|
||||
## Summary
|
||||
- **Files Checked:** $filesChecked
|
||||
- **Files with Conflicts:** $($filesWithConflicts.Count)
|
||||
|
||||
"@
|
||||
|
||||
if ($filesWithConflicts.Count -gt 0) {
|
||||
Write-Host "`n❌ Merge conflict markers detected in the following files:" -ForegroundColor Red
|
||||
|
||||
$summaryContent += "`n## ❌ Conflicts Detected`n`n"
|
||||
$summaryContent += "The following files contain merge conflict markers:`n`n"
|
||||
|
||||
foreach ($fileInfo in $filesWithConflicts) {
|
||||
Write-Host " - $($fileInfo.File)" -ForegroundColor Red
|
||||
|
||||
$summaryContent += "### 📄 ``$($fileInfo.File)```n`n"
|
||||
$summaryContent += "| Line | Marker |`n"
|
||||
$summaryContent += "|------|--------|`n"
|
||||
|
||||
foreach ($detail in $fileInfo.MarkerDetails) {
|
||||
Write-Host " Line $($detail.Line): $($detail.Marker)" -ForegroundColor Red
|
||||
$summaryContent += "| $($detail.Line) | ``$($detail.Marker)`` |`n"
|
||||
}
|
||||
$summaryContent += "`n"
|
||||
}
|
||||
|
||||
$summaryContent += "`n**Action Required:** Please resolve these conflicts before merging.`n"
|
||||
Write-Host "`nPlease resolve these conflicts before merging." -ForegroundColor Red
|
||||
} else {
|
||||
Write-Host "`n✅ No merge conflict markers found" -ForegroundColor Green
|
||||
$summaryContent += "`n## ✅ No Conflicts Found`n`nAll checked files are free of merge conflict markers.`n"
|
||||
}
|
||||
|
||||
$summaryContent | Out-File -FilePath $SummaryPath -Encoding utf8
|
||||
}
|
||||
|
||||
# Exit with error if conflicts found
|
||||
if ($filesWithConflicts.Count -gt 0) {
|
||||
throw "Merge conflict markers detected in $($filesWithConflicts.Count) file(s)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user