microsoft/hve-core
Publicmirrored fromhttps://github.com/microsoft/hve-coreAvailable
.github/skills/shared/pr-reference/scripts/read-diff.ps1
333lines · modecode
| 1 | # Copyright (c) Microsoft Corporation. |
| 2 | # SPDX-License-Identifier: MIT |
| 3 | #Requires -Version 7.0 |
| 4 | |
| 5 | <# |
| 6 | .SYNOPSIS |
| 7 | Reads diff content from a PR reference XML with chunking and file filtering. |
| 8 | |
| 9 | .DESCRIPTION |
| 10 | Provides structured access to pr-reference.xml content including chunk-based |
| 11 | reading, line range extraction, and single-file diff isolation. |
| 12 | |
| 13 | .PARAMETER InputPath |
| 14 | Path to the PR reference XML file. Defaults to .copilot-tracking/pr/pr-reference.xml. |
| 15 | |
| 16 | .PARAMETER Chunk |
| 17 | Chunk number to read (1-based). |
| 18 | |
| 19 | .PARAMETER ChunkSize |
| 20 | Number of lines per chunk. Defaults to 500. |
| 21 | |
| 22 | .PARAMETER Lines |
| 23 | Line range to read in format "START,END" or "START-END". |
| 24 | |
| 25 | .PARAMETER File |
| 26 | Extract diff for a specific file path. |
| 27 | |
| 28 | .PARAMETER Summary |
| 29 | Show diff summary with file list and change stats. |
| 30 | |
| 31 | .PARAMETER Info |
| 32 | Show chunk information without content. |
| 33 | |
| 34 | .EXAMPLE |
| 35 | ./read-diff.ps1 -Chunk 1 |
| 36 | Reads the first 500 lines of the diff. |
| 37 | |
| 38 | .EXAMPLE |
| 39 | ./read-diff.ps1 -Chunk 2 -ChunkSize 300 |
| 40 | Reads lines 301-600 of the diff. |
| 41 | |
| 42 | .EXAMPLE |
| 43 | ./read-diff.ps1 -File "src/main.ts" |
| 44 | Extracts the diff for a specific file. |
| 45 | |
| 46 | .EXAMPLE |
| 47 | ./read-diff.ps1 -Info |
| 48 | Shows chunk breakdown without content. |
| 49 | #> |
| 50 | |
| 51 | [CmdletBinding(DefaultParameterSetName = 'Default')] |
| 52 | param( |
| 53 | [Parameter()] |
| 54 | [Alias('i', 'Input')] |
| 55 | [string]$InputPath = "", |
| 56 | |
| 57 | [Parameter(ParameterSetName = 'Chunk')] |
| 58 | [Alias('c')] |
| 59 | [int]$Chunk = 0, |
| 60 | |
| 61 | [Parameter()] |
| 62 | [Alias('s')] |
| 63 | [int]$ChunkSize = 500, |
| 64 | |
| 65 | [Parameter(ParameterSetName = 'Lines')] |
| 66 | [Alias('l')] |
| 67 | [string]$Lines = "", |
| 68 | |
| 69 | [Parameter(ParameterSetName = 'File')] |
| 70 | [Alias('f')] |
| 71 | [string]$File = "", |
| 72 | |
| 73 | [Parameter(ParameterSetName = 'Summary')] |
| 74 | [switch]$Summary, |
| 75 | |
| 76 | [Parameter(ParameterSetName = 'Info')] |
| 77 | [switch]$Info |
| 78 | ) |
| 79 | |
| 80 | $ErrorActionPreference = 'Stop' |
| 81 | |
| 82 | Import-Module (Join-Path $PSScriptRoot 'shared.psm1') -Force |
| 83 | |
| 84 | function Get-ChunkInfo { |
| 85 | [OutputType([PSCustomObject])] |
| 86 | param( |
| 87 | [Parameter(Mandatory)] |
| 88 | [int]$TotalLines, |
| 89 | |
| 90 | [Parameter(Mandatory)] |
| 91 | [int]$ChunkSize |
| 92 | ) |
| 93 | |
| 94 | $totalChunks = [math]::Ceiling($TotalLines / $ChunkSize) |
| 95 | |
| 96 | return [PSCustomObject]@{ |
| 97 | TotalLines = $TotalLines |
| 98 | ChunkSize = $ChunkSize |
| 99 | TotalChunks = $totalChunks |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | function Get-ChunkRange { |
| 104 | [OutputType([PSCustomObject])] |
| 105 | param( |
| 106 | [Parameter(Mandatory)] |
| 107 | [int]$ChunkNumber, |
| 108 | |
| 109 | [Parameter(Mandatory)] |
| 110 | [int]$ChunkSize, |
| 111 | |
| 112 | [Parameter(Mandatory)] |
| 113 | [int]$TotalLines |
| 114 | ) |
| 115 | |
| 116 | $start = (($ChunkNumber - 1) * $ChunkSize) + 1 |
| 117 | $end = [math]::Min($ChunkNumber * $ChunkSize, $TotalLines) |
| 118 | |
| 119 | return [PSCustomObject]@{ |
| 120 | Start = $start |
| 121 | End = $end |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | function Get-FileDiff { |
| 126 | [OutputType([string])] |
| 127 | param( |
| 128 | [Parameter(Mandatory)] |
| 129 | [AllowEmptyString()] |
| 130 | [string[]]$Content, |
| 131 | |
| 132 | [Parameter(Mandatory)] |
| 133 | [string]$FilePath |
| 134 | ) |
| 135 | |
| 136 | $inTargetFile = $false |
| 137 | $diffLines = @() |
| 138 | $escapedPath = [regex]::Escape($FilePath) |
| 139 | |
| 140 | foreach ($line in $Content) { |
| 141 | if ($line -match "^diff --git") { |
| 142 | if ($line -match "a/$escapedPath b/") { |
| 143 | $inTargetFile = $true |
| 144 | } |
| 145 | elseif ($inTargetFile) { |
| 146 | break |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | if ($inTargetFile) { |
| 151 | $diffLines += $line |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | return $diffLines -join [Environment]::NewLine |
| 156 | } |
| 157 | |
| 158 | function Get-DiffSummary { |
| 159 | [OutputType([string])] |
| 160 | param( |
| 161 | [Parameter(Mandatory)] |
| 162 | [AllowEmptyString()] |
| 163 | [string[]]$Content |
| 164 | ) |
| 165 | |
| 166 | $files = @() |
| 167 | $currentFile = $null |
| 168 | $added = 0 |
| 169 | $removed = 0 |
| 170 | |
| 171 | foreach ($line in $Content) { |
| 172 | if ($line -match "^diff --git a/(.+?) b/") { |
| 173 | if ($currentFile) { |
| 174 | $files += [PSCustomObject]@{ |
| 175 | Path = $currentFile |
| 176 | Added = $added |
| 177 | Removed = $removed |
| 178 | } |
| 179 | } |
| 180 | $currentFile = $Matches[1] |
| 181 | $added = 0 |
| 182 | $removed = 0 |
| 183 | } |
| 184 | elseif ($currentFile) { |
| 185 | if ($line -match "^\+[^+]") { $added++ } |
| 186 | elseif ($line -match "^-[^-]") { $removed++ } |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | if ($currentFile) { |
| 191 | $files += [PSCustomObject]@{ |
| 192 | Path = $currentFile |
| 193 | Added = $added |
| 194 | Removed = $removed |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | $output = @("Changed files:") |
| 199 | foreach ($file in ($files | Sort-Object Path)) { |
| 200 | $output += " $($file.Path) (+$($file.Added)/-$($file.Removed))" |
| 201 | } |
| 202 | |
| 203 | return $output -join [Environment]::NewLine |
| 204 | } |
| 205 | |
| 206 | function Invoke-ReadDiff { |
| 207 | [CmdletBinding()] |
| 208 | param( |
| 209 | [Parameter()] |
| 210 | [string]$InputPath = "", |
| 211 | |
| 212 | [Parameter()] |
| 213 | [int]$Chunk = 0, |
| 214 | |
| 215 | [Parameter()] |
| 216 | [int]$ChunkSize = 500, |
| 217 | |
| 218 | [Parameter()] |
| 219 | [string]$Lines = "", |
| 220 | |
| 221 | [Parameter()] |
| 222 | [string]$File = "", |
| 223 | |
| 224 | [Parameter()] |
| 225 | [switch]$Summary, |
| 226 | |
| 227 | [Parameter()] |
| 228 | [switch]$Info |
| 229 | ) |
| 230 | |
| 231 | $repoRoot = Get-RepositoryRoot |
| 232 | $xmlPath = if ($InputPath) { |
| 233 | $InputPath |
| 234 | } else { |
| 235 | Join-Path $repoRoot '.copilot-tracking/pr/pr-reference.xml' |
| 236 | } |
| 237 | |
| 238 | if (-not (Test-Path -LiteralPath $xmlPath)) { |
| 239 | throw "PR reference file not found: $xmlPath`nRun generate.ps1 first to create the PR reference." |
| 240 | } |
| 241 | |
| 242 | $content = Get-Content -LiteralPath $xmlPath |
| 243 | $totalLines = $content.Count |
| 244 | $chunkInfo = Get-ChunkInfo -TotalLines $totalLines -ChunkSize $ChunkSize |
| 245 | |
| 246 | # Info mode |
| 247 | if ($Info) { |
| 248 | Write-Output "File: $xmlPath" |
| 249 | Write-Output "Total lines: $totalLines" |
| 250 | Write-Output "Chunk size: $ChunkSize" |
| 251 | Write-Output "Total chunks: $($chunkInfo.TotalChunks)" |
| 252 | Write-Output "" |
| 253 | Write-Output "Chunk breakdown:" |
| 254 | for ($i = 1; $i -le $chunkInfo.TotalChunks; $i++) { |
| 255 | $range = Get-ChunkRange -ChunkNumber $i -ChunkSize $ChunkSize -TotalLines $totalLines |
| 256 | Write-Output " Chunk ${i}: lines $($range.Start)-$($range.End)" |
| 257 | } |
| 258 | return |
| 259 | } |
| 260 | |
| 261 | # Summary mode |
| 262 | if ($Summary) { |
| 263 | $summaryOutput = Get-DiffSummary -Content $content |
| 264 | Write-Output $summaryOutput |
| 265 | return |
| 266 | } |
| 267 | |
| 268 | # File extraction mode |
| 269 | if ($File) { |
| 270 | $fileDiff = Get-FileDiff -Content $content -FilePath $File |
| 271 | if ($fileDiff) { |
| 272 | Write-Output $fileDiff |
| 273 | } |
| 274 | else { |
| 275 | Write-Warning "No diff found for file: $File" |
| 276 | } |
| 277 | return |
| 278 | } |
| 279 | |
| 280 | # Chunk mode |
| 281 | if ($Chunk -gt 0) { |
| 282 | if ($Chunk -gt $chunkInfo.TotalChunks) { |
| 283 | throw "Chunk $Chunk exceeds file (only $($chunkInfo.TotalChunks) chunks available)" |
| 284 | } |
| 285 | |
| 286 | $range = Get-ChunkRange -ChunkNumber $Chunk -ChunkSize $ChunkSize -TotalLines $totalLines |
| 287 | Write-Output "# Chunk $Chunk/$($chunkInfo.TotalChunks) (lines $($range.Start)-$($range.End) of $totalLines)" |
| 288 | Write-Output "" |
| 289 | $content[($range.Start - 1)..($range.End - 1)] | ForEach-Object { Write-Output $_ } |
| 290 | return |
| 291 | } |
| 292 | |
| 293 | # Line range mode |
| 294 | if ($Lines) { |
| 295 | $rangeParts = $Lines -replace ',', '-' -split '-' |
| 296 | if ($rangeParts.Count -ne 2) { |
| 297 | throw "Invalid line range format. Use START,END or START-END" |
| 298 | } |
| 299 | |
| 300 | $start = [int]$rangeParts[0] |
| 301 | $end = [math]::Min([int]$rangeParts[1], $totalLines) |
| 302 | |
| 303 | if ($start -gt $totalLines) { |
| 304 | throw "Start line $start exceeds file length ($totalLines lines)" |
| 305 | } |
| 306 | |
| 307 | Write-Output "# Lines $start-$end of $totalLines" |
| 308 | Write-Output "" |
| 309 | $content[($start - 1)..($end - 1)] | ForEach-Object { Write-Output $_ } |
| 310 | return |
| 311 | } |
| 312 | |
| 313 | # Default: show info |
| 314 | Write-Output "File: $xmlPath" |
| 315 | Write-Output "Total lines: $totalLines" |
| 316 | Write-Output "Total chunks: $($chunkInfo.TotalChunks) (at $ChunkSize lines/chunk)" |
| 317 | Write-Output "" |
| 318 | Write-Output "Use -Chunk N to read a specific chunk, -Lines START,END for a range," |
| 319 | Write-Output "or -File PATH to extract a specific file's diff." |
| 320 | } |
| 321 | |
| 322 | #region Main Execution |
| 323 | if ($MyInvocation.InvocationName -ne '.') { |
| 324 | try { |
| 325 | Invoke-ReadDiff -InputPath $InputPath -Chunk $Chunk -ChunkSize $ChunkSize -Lines $Lines -File $File -Summary:$Summary -Info:$Info |
| 326 | exit 0 |
| 327 | } |
| 328 | catch { |
| 329 | Write-Error -ErrorAction Continue $_.Exception.Message |
| 330 | exit 1 |
| 331 | } |
| 332 | } |
| 333 | #endregion |
| 334 | |