microsoft/hve-core

Public

mirrored from https://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
251021ec2b16fc350c0c33ddff5c1e09cfd57943

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/dev-tools/Generate-PrReference.ps1

497lines · modecode

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