Add correct header ps1 comment

This commit is contained in:
Gordon Lam (SH)
2025-09-30 13:20:04 +08:00
parent e963125210
commit 0371b4134c
4 changed files with 198 additions and 56 deletions

View File

@@ -1,23 +1,59 @@
param(
[string]$BaseCsv = ".\sorted_prs_93_round1.csv",
[string]$AllCsv = ".\sorted_prs.csv",
[string]$OutCsv = ".\sorted_prs_93_incremental.csv",
[string]$Key = "Number"
<#
.SYNOPSIS
Produce an incremental PR CSV containing rows present in a newer full export but absent from a baseline export.
.DESCRIPTION
Compares two previously generated sorted PR CSV files (same schema). Any row whose key column value
(defaults to 'Number') does not exist in the baseline file is emitted to a new incremental CSV, preserving
the original column order. If no new rows are found, an empty CSV (with headers when determinable) is written.
.PARAMETER BaseCsv
Path to the baseline (earlier) PR CSV.
.PARAMETER AllCsv
Path to the newer full PR CSV containing superset (or equal set) of rows.
.PARAMETER OutCsv
Path to write the incremental CSV containing only new rows.
.PARAMETER Key
Column name used as unique identifier (defaults to 'Number'). Must exist in both CSVs.
.EXAMPLE
pwsh ./diff_prs.ps1 -BaseCsv sorted_prs_prev.csv -AllCsv sorted_prs.csv -OutCsv sorted_prs_incremental.csv
.NOTES
Requires: PowerShell 7+, both CSVs with identical column schemas.
Exit code 0 on success (even if zero incremental rows). Throws on missing files.
#>
[CmdletBinding()] param(
[Parameter(Mandatory=$false)][string]$BaseCsv = "./sorted_prs_93_round1.csv",
[Parameter(Mandatory=$false)][string]$AllCsv = "./sorted_prs.csv",
[Parameter(Mandatory=$false)][string]$OutCsv = "./sorted_prs_93_incremental.csv",
[Parameter(Mandatory=$false)][string]$Key = "Number"
)
# Fail on errors
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
if (-not (Test-Path -LiteralPath $BaseCsv)) {
throw "Base CSV not found: $BaseCsv"
}
if (-not (Test-Path -LiteralPath $AllCsv)) {
throw "All CSV not found: $AllCsv"
}
function Write-Info($m) { Write-Host "[info] $m" -ForegroundColor Cyan }
function Write-Warn($m) { Write-Host "[warn] $m" -ForegroundColor Yellow }
if (-not (Test-Path -LiteralPath $BaseCsv)) { throw "Base CSV not found: $BaseCsv" }
if (-not (Test-Path -LiteralPath $AllCsv)) { throw "All CSV not found: $AllCsv" }
# Load CSVs
$baseRows = Import-Csv -LiteralPath $BaseCsv
$allRows = Import-Csv -LiteralPath $AllCsv
if (-not $baseRows) { Write-Warn "Base CSV has no rows." }
if (-not $allRows) { Write-Warn "All CSV has no rows." }
# Validate key presence
if ($baseRows -and -not ($baseRows[0].PSObject.Properties.Name -contains $Key)) { throw "Key column '$Key' not found in base CSV." }
if ($allRows -and -not ($allRows[0].PSObject.Properties.Name -contains $Key)) { throw "Key column '$Key' not found in all CSV." }
# Build a set of existing keys from base
$set = New-Object 'System.Collections.Generic.HashSet[string]'
foreach ($row in $baseRows) {
@@ -25,7 +61,7 @@ foreach ($row in $baseRows) {
if ($null -ne $val) { [void]$set.Add($val) }
}
# Filter rows in AllCsv whose key is not in base
# Filter rows in AllCsv whose key is not in base (these are the new / incremental rows)
$incremental = @()
foreach ($row in $allRows) {
$val = [string]($row.$Key)
@@ -38,23 +74,27 @@ if ($allRows.Count -gt 0) {
$columns = $allRows[0].PSObject.Properties.Name
}
if ($incremental.Count -gt 0) {
if ($columns.Count -gt 0) {
$incremental | Select-Object -Property $columns | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
try {
if ($incremental.Count -gt 0) {
if ($columns.Count -gt 0) {
$incremental | Select-Object -Property $columns | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
} else {
$incremental | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
}
} else {
$incremental | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
}
} else {
# Write an empty CSV with headers if we know them
if ($columns.Count -gt 0) {
# Create one empty object with the same properties to export just headers
$obj = [PSCustomObject]@{}
foreach ($c in $columns) { $obj | Add-Member -NotePropertyName $c -NotePropertyValue $null }
$obj | Select-Object -Property $columns | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
} else {
'' | Out-File -LiteralPath $OutCsv -Encoding UTF8
# Write an empty CSV with headers if we know them (facilitates downstream tooling expecting header row)
if ($columns.Count -gt 0) {
$obj = [PSCustomObject]@{}
foreach ($c in $columns) { $obj | Add-Member -NotePropertyName $c -NotePropertyValue $null }
$obj | Select-Object -Property $columns | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
} else {
'' | Out-File -LiteralPath $OutCsv -Encoding UTF8
}
}
Write-Info ("Incremental rows: {0}" -f $incremental.Count)
Write-Info ("Output: {0}" -f (Resolve-Path -LiteralPath $OutCsv))
}
catch {
Write-Host "[error] Failed writing output CSV: $_" -ForegroundColor Red
exit 1
}
Write-Host ("Incremental rows: {0}" -f $incremental.Count)
Write-Host ("Output: {0}" -f (Resolve-Path -LiteralPath $OutCsv))

View File

@@ -1,23 +1,73 @@
# === CONFIGURATION ===
$repo = "microsoft/Powertoys" # e.g., "microsoft/winget-cli"
$milestone = "PowerToys 0.94" # Milestone title
$outputJson = "milestone_prs.json"
$outputCsv = "sorted_prs.csv"
<#
.SYNOPSIS
Export merged pull requests for a milestone into JSON and CSV (sorted) with optional Copilot review summarization.
.DESCRIPTION
Uses the GitHub CLI (gh) to list merged PRs for the specified milestone, captures basic metadata,
attempts to obtain a Copilot review summary (choosing the longest Copilot-authored review body),
filters labels to a predefined allow-list, and outputs:
* Raw JSON list (for traceability)
* Sorted CSV (first label alphabetical) used by downstream grouping scripts.
.PARAMETER Repo
GitHub repository in the form 'owner/name'. Default: 'microsoft/PowerToys'.
.PARAMETER Milestone
Exact milestone title (as it appears on GitHub), e.g. 'PowerToys 0.95'.
.PARAMETER OutputJson
Path for raw JSON output. Default: 'milestone_prs.json'.
.PARAMETER OutputCsv
Path for sorted CSV output. Default: 'sorted_prs.csv'.
.EXAMPLE
pwsh ./dump-prs-information.ps1 -Milestone 'PowerToys 0.95'
.EXAMPLE
pwsh ./dump-prs-information.ps1 -Repo microsoft/PowerToys -Milestone 'PowerToys 0.95' -OutputCsv m1.csv
.NOTES
Requires: gh CLI authenticated with repo read access.
This script intentionally does NOT use Set-StrictMode (per current repository guidance for release tooling).
#>
[CmdletBinding()] param(
[Parameter(Mandatory=$false)][string]$Repo = 'microsoft/PowerToys',
[Parameter(Mandatory=$true)][string]$Milestone,
[Parameter(Mandatory=$false)][string]$OutputJson = 'milestone_prs.json',
[Parameter(Mandatory=$false)][string]$OutputCsv = 'sorted_prs.csv'
)
$ErrorActionPreference = 'Stop'
function Write-Info($m){ Write-Host "[info] $m" -ForegroundColor Cyan }
function Write-Warn($m){ Write-Host "[warn] $m" -ForegroundColor Yellow }
function Write-Err($m){ Write-Host "[error] $m" -ForegroundColor Red }
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { Write-Err "GitHub CLI 'gh' not found in PATH."; exit 1 }
Write-Info "Fetching merged PRs for milestone '$Milestone' from $Repo ..."
$searchQuery = "milestone:`"$Milestone`""
$ghCommand = "gh pr list --repo $Repo --state merged --search '$searchQuery' --json number,title,labels,author,url,body --limit 200"
try {
Invoke-Expression $ghCommand | Out-File -Encoding UTF8 -FilePath $OutputJson
}
catch {
Write-Err "Failed querying PRs: $_"; exit 1
}
# === STEP 1: Query PRs from GitHub ===
Write-Host "Fetching PRs for milestone '$milestone'..."
$searchQuery = "milestone:`"$($milestone)`""
$ghCommand = "gh pr list --repo $repo --state merged --search '$searchQuery' --json number,title,labels,author,url,body --limit 200"
Invoke-Expression "$ghCommand" | Out-File -Encoding UTF8 -FilePath $outputJson
if (-not (Test-Path -LiteralPath $OutputJson)) { Write-Err "JSON output not created: $OutputJson"; exit 1 }
# === STEP 2: Parse and Sort ===
$prs = Get-Content $outputJson | ConvertFrom-Json
Write-Info "Parsing JSON ..."
$prs = Get-Content $OutputJson | ConvertFrom-Json
if (-not $prs) { Write-Warn "No PRs returned for milestone '$Milestone'"; exit 0 }
$sorted = $prs | Sort-Object { $_.labels[0]?.name }
Write-Host "Fetching Copilot reviews for each PR..."
Write-Info "Fetching Copilot reviews for each PR (longest Copilot-authored body)."
$csvData = $sorted | ForEach-Object {
$prNumber = $_.number
Write-Host "Processing PR #$prNumber..."
Write-Info "Processing PR #$prNumber ..."
# Get Copilot review for this PR
$copilotOverview = ""
@@ -37,13 +87,13 @@ $csvData = $sorted | ForEach-Object {
if ($copilotReviews -and $copilotReviews.Count -gt 0) {
$longest = $copilotReviews | Sort-Object { $_.body.Length } -Descending | Select-Object -First 1
$copilotOverview = $longest.body.Replace("`r", "").Replace("`n", " ") -replace '\s+', ' '
Write-Host " Selected Copilot review (author=$($longest.author.login) length=$($longest.body.Length))"
Write-Info " Copilot review selected (author=$($longest.author.login) length=$($longest.body.Length))"
} else {
Write-Host " No Copilot reviews found for PR #$prNumber"
Write-Warn " No Copilot reviews found for PR #$prNumber"
}
}
catch {
Write-Host " Warning: Could not fetch reviews for PR #$prNumber"
Write-Warn " Could not fetch reviews for PR #$prNumber"
}
# Filter labels to only include specific patterns
@@ -68,6 +118,6 @@ $csvData = $sorted | ForEach-Object {
}
# === STEP 3: Output CSV ===
Write-Host "Saving to $outputCsv..."
$csvData | Export-Csv $outputCsv -NoTypeInformation
Write-Host "Done. Open '$outputCsv' to group PRs and send them back."
Write-Info "Saving CSV to $OutputCsv ..."
$csvData | Export-Csv $OutputCsv -NoTypeInformation -Encoding UTF8
Write-Info "Done. Rows: $($csvData.Count). CSV: $(Resolve-Path -LiteralPath $OutputCsv)"

View File

@@ -1,3 +1,36 @@
<#
.SYNOPSIS
Export merged PR metadata between two commits (exclusive start, inclusive end) to JSON and CSV.
.DESCRIPTION
Identifies merge/squash commits reachable from EndCommit but not StartCommit, extracts PR numbers,
queries GitHub for metadata plus (optionally) Copilot review/comment summaries, filters labels, then
emits a JSON artifact and a sorted CSV (first label alphabetical) analogous to dump-prs-information.ps1.
.PARAMETER StartCommit
Exclusive starting commit (SHA, tag, or ref). Commits AFTER this one are considered.
.PARAMETER EndCommit
Inclusive ending commit (SHA, tag, or ref). Default: HEAD.
.PARAMETER Repo
GitHub repository (owner/name). Default: microsoft/PowerToys.
.PARAMETER OutputCsv
Destination CSV path. Default: sorted_prs.csv.
.PARAMETER OutputJson
Destination JSON path containing raw PR objects. Default: milestone_prs.json.
.EXAMPLE
pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd
.EXAMPLE
pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd -EndCommit 89ef7654 -OutputCsv delta.csv
.NOTES
Requires: git, gh (authenticated). No Set-StrictMode to keep parity with existing release scripts.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][string]$StartCommit, # exclusive start (commits AFTER this one)

View File

@@ -1,6 +1,21 @@
<#
Groups PRs from sorted_prs.csv by Labels and emits per-label CSV files.
Each output CSV keeps the original columns and the same PR order as in the input.
.SYNOPSIS
Group PR rows by their Labels column and emit per-label CSV files.
.DESCRIPTION
Reads a milestone PR CSV (usually produced by dump-prs-information / dump-prs-since-commit scripts),
splits rows by label list, normalizes/sorts individual labels, and writes one CSV per unique label combination.
Each output preserves the original row ordering within that subset and column order from the source.
.PARAMETER CsvPath
Input CSV containing PR rows with a 'Labels' column (comma-separated list).
.PARAMETER OutDir
Output directory to place grouped CSVs (created if missing). Default: 'grouped_csv'.
.NOTES
Label combinations are joined using ' | ' when multiple labels present. Filenames are sanitized (invalid characters,
whitespace collapsed) and truncated to <= 120 characters.
#>
param(
[string]$CsvPath = "sorted_prs.csv",
@@ -18,9 +33,13 @@ Write-Info "Reading CSV: $CsvPath"
$rows = Import-Csv -LiteralPath $CsvPath
Write-Info ("Loaded {0} rows" -f $rows.Count)
function Sanitize-FileName([string]$name) {
if ([string]::IsNullOrWhiteSpace($name)) { return 'Unnamed' }
$s = $name -replace '[<>:"/\\|?*]', '-' # invalid path chars
function ConvertTo-SafeFileName {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$Name
)
if ([string]::IsNullOrWhiteSpace($Name)) { return 'Unnamed' }
$s = $Name -replace '[<>:"/\\|?*]', '-' # invalid path chars
$s = $s -replace '\s+', '-' # spaces to dashes
$s = $s -replace '-{2,}', '-' # collapse dashes
$s = $s.Trim('-')
@@ -29,7 +48,7 @@ function Sanitize-FileName([string]$name) {
return $s
}
# Group rows by label combination; preserve CSV order inside each group
# Build groups keyed by normalized, sorted label combinations. Preserve original CSV row order.
$groups = @{}
foreach ($row in $rows) {
$labelsRaw = $row.Labels
@@ -55,7 +74,7 @@ Write-Info ("Generating {0} grouped CSV file(s) into: {1}" -f $groups.Count, $Ou
foreach ($key in $groups.Keys) {
$labelParts = if ($key -eq 'Unlabeled') { @('Unlabeled') } else { $key -split '\s\|\s' }
$safeName = ($labelParts | ForEach-Object { Sanitize-FileName $_ }) -join '-'
$safeName = ($labelParts | ForEach-Object { ConvertTo-SafeFileName -Name $_ }) -join '-'
$filePath = Join-Path $OutDir ("$safeName.csv")
# Keep same columns and order