microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ci/884-codeql-python-analysis

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/skills/shared/pr-reference/scripts/generate.ps1

502lines · modecode

1# Copyright (c) Microsoft Corporation.
2# SPDX-License-Identifier: MIT
3#Requires -Version 7.0
4
5<#
6.SYNOPSIS
7Generates the Copilot PR reference XML using git history and diff data.
8
9.DESCRIPTION
10Creates a pr-reference.xml file (default: .copilot-tracking/pr/pr-reference.xml)
11relative to the repository root, mirroring the behaviour of generate.sh. Supports
12excluding markdown files from the diff and specifying an alternate base branch
13for comparisons.
14
15.PARAMETER BaseBranch
16Git branch used as the comparison base. Defaults to "main".
17
18.PARAMETER ExcludeMarkdownDiff
19When supplied, excludes markdown (*.md) files from the diff output.
20
21.PARAMETER OutputPath
22Custom output file path. When empty, defaults to
23.copilot-tracking/pr/pr-reference.xml relative to the repository root.
24#>
25
26[CmdletBinding()]
27param(
28 [Parameter()]
29 [string]$BaseBranch = "main",
30
31 [Parameter()]
32 [switch]$ExcludeMarkdownDiff,
33
34 [Parameter()]
35 [string]$OutputPath = ""
36)
37
38$ErrorActionPreference = 'Stop'
39
40Import-Module (Join-Path $PSScriptRoot 'shared.psm1') -Force
41
42function Test-GitAvailability {
43<#
44.SYNOPSIS
45Verifies the git executable is available.
46.DESCRIPTION
47Throws a terminating error when git can't be resolved from PATH.
48#>
49 [OutputType([void])]
50 param()
51
52 if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
53 throw "Git is required but was not found on PATH."
54 }
55}
56
57function New-PrDirectory {
58<#
59.SYNOPSIS
60Creates the parent directory for the output file when missing.
61.DESCRIPTION
62Ensures the parent directory of the specified path exists.
63.PARAMETER OutputFilePath
64Absolute path to the output file whose parent directory should be created.
65.OUTPUTS
66System.String
67#>
68 [CmdletBinding(SupportsShouldProcess = $true)]
69 [OutputType([string])]
70 param(
71 [Parameter(Mandatory = $true)]
72 [string]$OutputFilePath
73 )
74
75 $parentDir = Split-Path -Parent $OutputFilePath
76 if (-not (Test-Path $parentDir)) {
77 if ($PSCmdlet.ShouldProcess($parentDir, 'Create output directory')) {
78 $null = New-Item -ItemType Directory -Path $parentDir -Force
79 }
80 }
81
82 return $parentDir
83}
84
85function Resolve-ComparisonReference {
86<#
87.SYNOPSIS
88Resolves the git reference used for comparisons.
89.DESCRIPTION
90Prefers origin/<BaseBranch> when available and falls back to the provided branch.
91.PARAMETER BaseBranch
92Branch name supplied by the caller.
93.OUTPUTS
94PSCustomObject
95#>
96 [OutputType([PSCustomObject])]
97 param(
98 [Parameter(Mandatory = $true)]
99 [string]$BaseBranch
100 )
101
102 $candidates = @()
103 if ($BaseBranch -notlike 'origin/*' -and $BaseBranch -notlike 'refs/*') {
104 $candidates += "origin/$BaseBranch"
105 }
106 $candidates += $BaseBranch
107
108 foreach ($candidate in $candidates) {
109 & git rev-parse --verify $candidate *> $null
110 if ($LASTEXITCODE -eq 0) {
111 $label = if ($candidate -eq $BaseBranch) {
112 $BaseBranch
113 } else {
114 "$BaseBranch (via $candidate)"
115 }
116
117 return [PSCustomObject]@{
118 Ref = $candidate
119 Label = $label
120 }
121 }
122 }
123
124 throw "Branch '$BaseBranch' does not exist or is not accessible."
125}
126
127function Get-ShortCommitHash {
128<#
129.SYNOPSIS
130Retrieves the short commit hash for a ref.
131.DESCRIPTION
132Uses git rev-parse --short to resolve the supplied ref.
133.PARAMETER Ref
134Git reference to resolve.
135.OUTPUTS
136System.String
137#>
138 [OutputType([string])]
139 param(
140 [Parameter(Mandatory = $true)]
141 [string]$Ref
142 )
143
144 $commit = (& git rev-parse --short $Ref).Trim()
145 if ($LASTEXITCODE -ne 0) {
146 throw "Failed to resolve ref '$Ref'."
147 }
148
149 return $commit
150}
151
152function Get-CurrentBranchOrRef {
153<#
154.SYNOPSIS
155Retrieves the current branch name or a fallback reference.
156.DESCRIPTION
157Returns the current branch name when on a branch. In detached HEAD state
158(common in CI environments), falls back to a short commit SHA prefixed with
159'detached@'.
160.OUTPUTS
161System.String
162#>
163 [OutputType([string])]
164 param()
165
166 $branchOutput = & git --no-pager branch --show-current 2>$null
167 if ($branchOutput) {
168 return $branchOutput.Trim()
169 }
170
171 # Detached HEAD - fall back to short SHA
172 $sha = (& git rev-parse --short HEAD 2>$null)
173 if ($LASTEXITCODE -eq 0 -and $sha) {
174 return "detached@$($sha.Trim())"
175 }
176
177 return 'unknown'
178}
179
180function Get-CommitEntry {
181<#
182.SYNOPSIS
183Collects formatted commit metadata.
184.DESCRIPTION
185Runs git log to gather commit entries relative to the supplied comparison ref.
186.PARAMETER ComparisonRef
187Git reference that acts as the diff base.
188.OUTPUTS
189System.String[]
190#>
191 [OutputType([string[]])]
192 param(
193 [Parameter(Mandatory = $true)]
194 [string]$ComparisonRef
195 )
196
197 $logArgs = @(
198 '--no-pager',
199 'log',
200 '--pretty=format:<commit hash="%h" date="%cd"><message><subject><![CDATA[%s]]></subject><body><![CDATA[%b]]></body></message></commit>',
201 '--date=short',
202 "${ComparisonRef}..HEAD"
203 )
204
205 $entries = & git @logArgs
206 if ($LASTEXITCODE -ne 0) {
207 throw "Failed to retrieve commit history."
208 }
209
210 return $entries
211}
212
213function Get-CommitCount {
214<#
215.SYNOPSIS
216Counts commits between HEAD and the comparison ref.
217.DESCRIPTION
218Executes git rev-list --count to measure branch divergence.
219.PARAMETER ComparisonRef
220Git reference that acts as the diff base.
221.OUTPUTS
222System.Int32
223#>
224 [OutputType([int])]
225 param(
226 [Parameter(Mandatory = $true)]
227 [string]$ComparisonRef
228 )
229
230 $countText = (& git --no-pager rev-list --count "${ComparisonRef}..HEAD").Trim()
231 if ($LASTEXITCODE -ne 0) {
232 throw "Failed to count commits."
233 }
234
235 if (-not $countText) {
236 return 0
237 }
238
239 return [int]$countText
240}
241
242function Get-DiffOutput {
243<#
244.SYNOPSIS
245Builds the git diff output for the comparison ref.
246.DESCRIPTION
247Runs git diff against the comparison ref with optional markdown exclusion.
248.PARAMETER ComparisonRef
249Git reference that acts as the diff base.
250.PARAMETER ExcludeMarkdownDiff
251Switch to omit markdown files from the diff.
252.OUTPUTS
253System.String[]
254#>
255 [OutputType([string[]])]
256 param(
257 [Parameter(Mandatory = $true)]
258 [string]$ComparisonRef,
259
260 [Parameter()]
261 [switch]$ExcludeMarkdownDiff
262 )
263
264 $diffArgs = @('--no-pager', 'diff', $ComparisonRef)
265 if ($ExcludeMarkdownDiff) {
266 $diffArgs += @('--', ':!*.md')
267 }
268
269 $diffOutput = & git @diffArgs
270 if ($LASTEXITCODE -ne 0) {
271 throw "Failed to retrieve diff output."
272 }
273
274 return $diffOutput
275}
276
277function Get-DiffSummary {
278<#
279.SYNOPSIS
280Summarizes the diff for quick reporting.
281.DESCRIPTION
282Uses git diff --shortstat against the comparison ref.
283.PARAMETER ComparisonRef
284Git reference that acts as the diff base.
285.PARAMETER ExcludeMarkdownDiff
286Switch to omit markdown files from the summary.
287.OUTPUTS
288System.String
289#>
290 [OutputType([string])]
291 param(
292 [Parameter(Mandatory = $true)]
293 [string]$ComparisonRef,
294
295 [Parameter()]
296 [switch]$ExcludeMarkdownDiff
297 )
298
299 $diffStatArgs = @('--no-pager', 'diff', '--shortstat', $ComparisonRef)
300 if ($ExcludeMarkdownDiff) {
301 $diffStatArgs += @('--', ':!*.md')
302 }
303
304 $summary = & git @diffStatArgs
305 if ($LASTEXITCODE -ne 0) {
306 throw "Failed to summarize diff output."
307 }
308
309 if (-not $summary) {
310 return '0 files changed'
311 }
312
313 return $summary
314}
315
316function Get-PrXmlContent {
317<#
318.SYNOPSIS
319Constructs the PR reference XML document.
320.DESCRIPTION
321Creates XML containing the current branch, base branch, commits, and diff.
322.PARAMETER CurrentBranch
323Name of the active git branch.
324.PARAMETER BaseBranch
325Branch used as the base reference.
326.PARAMETER CommitEntries
327Formatted commit entries produced by Get-CommitEntry.
328.PARAMETER DiffOutput
329Diff lines produced by Get-DiffOutput.
330.OUTPUTS
331System.String
332#>
333 [OutputType([string])]
334 param(
335 [Parameter(Mandatory = $true)]
336 [string]$CurrentBranch,
337
338 [Parameter(Mandatory = $true)]
339 [string]$BaseBranch,
340
341 [Parameter()]
342 [string[]]$CommitEntries,
343
344 [Parameter()]
345 [string[]]$DiffOutput
346 )
347
348 $commitBlock = if ($CommitEntries) {
349 ($CommitEntries | ForEach-Object { " $_" }) -join [Environment]::NewLine
350 } else {
351 ""
352 }
353
354 $diffBlock = if ($DiffOutput) {
355 ($DiffOutput | ForEach-Object { " $_" }) -join [Environment]::NewLine
356 } else {
357 ""
358 }
359
360 return @"
361<commit_history>
362 <current_branch>
363 $CurrentBranch
364 </current_branch>
365
366 <base_branch>
367 $BaseBranch
368 </base_branch>
369
370 <commits>
371$commitBlock
372 </commits>
373
374 <full_diff>
375$diffBlock
376 </full_diff>
377</commit_history>
378"@
379}
380
381function Get-LineImpact {
382<#
383.SYNOPSIS
384Calculates total line impact from a diff summary.
385.DESCRIPTION
386Parses insertion and deletion counts from git diff --shortstat output.
387.PARAMETER DiffSummary
388Short diff summary text.
389.OUTPUTS
390System.Int32
391#>
392 [OutputType([int])]
393 param(
394 [Parameter(Mandatory = $true)]
395 [string]$DiffSummary
396 )
397
398 $lineImpact = 0
399 if ($DiffSummary -match '(\d+) insertions') {
400 $lineImpact += [int]$matches[1]
401 }
402 if ($DiffSummary -match '(\d+) deletions') {
403 $lineImpact += [int]$matches[1]
404 }
405
406 return $lineImpact
407}
408
409function Invoke-PrReferenceGeneration {
410<#
411.SYNOPSIS
412Generates the pr-reference.xml file.
413.DESCRIPTION
414Coordinates git queries, XML creation, and console reporting for Copilot usage.
415.PARAMETER BaseBranch
416Branch used as the comparison base.
417.PARAMETER ExcludeMarkdownDiff
418Switch to omit markdown files from the diff and summary.
419.PARAMETER OutputPath
420Custom output file path. When empty, defaults to
421.copilot-tracking/pr/pr-reference.xml relative to the repository root.
422.OUTPUTS
423System.IO.FileInfo
424#>
425 [OutputType([System.IO.FileInfo])]
426 param(
427 [Parameter(Mandatory = $true)]
428 [string]$BaseBranch,
429
430 [Parameter()]
431 [switch]$ExcludeMarkdownDiff,
432
433 [Parameter()]
434 [string]$OutputPath = ""
435 )
436
437 Test-GitAvailability
438
439 $repoRoot = Get-RepositoryRoot -Strict
440
441 if ($OutputPath) {
442 $prReferencePath = $OutputPath
443 } else {
444 $prReferencePath = Join-Path $repoRoot '.copilot-tracking/pr/pr-reference.xml'
445 }
446
447 $null = New-PrDirectory -OutputFilePath $prReferencePath
448
449 $diffSummary = '0 files changed'
450 $commitCount = 0
451 $comparisonInfo = $null
452 $baseCommit = ''
453
454 Push-Location $repoRoot
455 try {
456 $currentBranch = Get-CurrentBranchOrRef
457 $comparisonInfo = Resolve-ComparisonReference -BaseBranch $BaseBranch
458 $baseCommit = Get-ShortCommitHash -Ref $comparisonInfo.Ref
459 $commitEntries = Get-CommitEntry -ComparisonRef $comparisonInfo.Ref
460 $commitCount = Get-CommitCount -ComparisonRef $comparisonInfo.Ref
461 $diffOutput = Get-DiffOutput -ComparisonRef $comparisonInfo.Ref -ExcludeMarkdownDiff:$ExcludeMarkdownDiff
462 $diffSummary = Get-DiffSummary -ComparisonRef $comparisonInfo.Ref -ExcludeMarkdownDiff:$ExcludeMarkdownDiff
463
464 $xmlContent = Get-PrXmlContent -CurrentBranch $currentBranch -BaseBranch $BaseBranch -CommitEntries $commitEntries -DiffOutput $diffOutput
465 $xmlContent | Set-Content -LiteralPath $prReferencePath
466 }
467 finally {
468 Pop-Location
469 }
470
471 $lineCount = (Get-Content -LiteralPath $prReferencePath).Count
472 $lineImpact = Get-LineImpact -DiffSummary $diffSummary
473
474 Write-Host "Created $prReferencePath"
475 if ($ExcludeMarkdownDiff) {
476 Write-Host 'Note: Markdown files were excluded from diff output'
477 }
478 Write-Host "Lines: $lineCount"
479 Write-Host "Base branch: $($comparisonInfo.Label) (@ $baseCommit)"
480 Write-Host "Commits compared: $commitCount"
481 Write-Host "Diff summary: $diffSummary"
482
483 if ($lineImpact -gt 1000) {
484 Write-Host 'Large diff detected. Rebase onto the intended base branch or narrow your changes if this scope is unexpected.'
485 }
486
487 return Get-Item -LiteralPath $prReferencePath
488}
489
490#region Main Execution
491if ($MyInvocation.InvocationName -ne '.') {
492 try {
493 Invoke-PrReferenceGeneration -BaseBranch $BaseBranch -ExcludeMarkdownDiff:$ExcludeMarkdownDiff -OutputPath $OutputPath | Out-Null
494 exit 0
495 }
496 catch {
497 Write-Error -ErrorAction Continue "Generate PR Reference failed: $($_.Exception.Message)"
498 Write-Warning "PR reference generation failed: $($_.Exception.Message)"
499 exit 1
500 }
501}
502#endregion
503