microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0bce418ef9a17e5e311d7cc01dc4e8ac699aa51f

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/dev-tools/Generate-PrReference.ps1

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