added documetation

This commit is contained in:
Jurjen Ladenius
2025-11-03 08:12:01 +01:00
parent 8840b0e300
commit a226ca97ac
37 changed files with 8315 additions and 1481 deletions

View File

@@ -1,20 +1,129 @@
# PowerShell script to analyze renovate PRs across repositories with detailed statistics
<#
.SYNOPSIS
Analyzes Renovate pull requests across all repositories in an Azure DevOps project and generates detailed statistics.
.DESCRIPTION
This script connects to an Azure DevOps organization and project to analyze Renovate dependency update pull requests.
It processes all repositories (active, disabled, and locked) and generates comprehensive statistics including:
- Total Renovate PRs per repository
- Open, completed, and abandoned PR counts
- Latest creation and completion dates
- Branch information for latest PRs
- Repository status (active, disabled, locked, error)
The script outputs both a detailed text report and a CSV file for further analysis.
Renovate PRs are identified by their branch naming pattern: "refs/heads/renovate/*"
.PARAMETER Organization
The Azure DevOps organization name. Defaults to "effectory" if not specified.
.PARAMETER Project
The Azure DevOps project name. Defaults to "Survey Software" if not specified.
.PARAMETER PAT
The Azure DevOps Personal Access Token (PAT) used for authentication. This token must have
'Code (Read)' permissions to access repository and pull request information.
.PARAMETER OutputFile
The path and filename for the output text report. Defaults to a timestamped filename:
"RenovatePRs_Stats_yyyyMMdd_HHmmss.txt"
.EXAMPLE
.\renovate-stats.ps1 -PAT "your-personal-access-token"
Analyzes Renovate PRs using default organization and project settings.
.EXAMPLE
.\renovate-stats.ps1 -PAT "your-pat-token" -Organization "myorg" -Project "MyProject"
Analyzes Renovate PRs for a specific organization and project.
.EXAMPLE
.\renovate-stats.ps1 -PAT "your-token" -OutputFile "C:\Reports\renovate-analysis.txt"
Analyzes Renovate PRs and saves the report to a custom location.
.OUTPUTS
Creates two output files:
1. Text Report: Detailed statistics with tables and summaries (.txt)
2. CSV Export: Raw data for further analysis (.csv)
The text report includes:
- Repository-by-repository statistics table (sorted by last created date)
- Summary statistics (total repos, repos with/without Renovate, PR counts)
- List of disabled/locked/error repositories
The CSV contains columns:
- Repository: Repository name
- TotalRenovatePRs: Total count of Renovate PRs
- OpenPRs: Count of active Renovate PRs
- CompletedPRs: Count of completed Renovate PRs
- AbandonedPRs: Count of abandoned Renovate PRs
- LastCreated: Date of most recent Renovate PR creation (yyyy-MM-dd format)
- LastCompleted: Date of most recent Renovate PR completion (yyyy-MM-dd format)
- LatestOpenBranch: Branch name of most recent open Renovate PR
- LatestCompletedBranch: Branch name of most recent completed Renovate PR
- LastCompletedPRTitle: Title of most recent completed Renovate PR
.NOTES
Author: Cloud Engineering Team
Created: 2025
Requires: PowerShell 5.1 or later
Dependencies: Internet connectivity to Azure DevOps
The script uses Azure DevOps REST API version 6.0 to retrieve repository and pull request information.
Ensure your Personal Access Token has the necessary permissions before running the script.
Renovate is a dependency update tool that creates automated pull requests. This script specifically
looks for PRs with branch names matching the pattern "refs/heads/renovate/*"
The script handles various repository states:
- Active: Normal repositories that can be analyzed
- Disabled: Repositories marked as disabled in Azure DevOps
- Locked: Repositories that are locked (read-only)
- Error: Repositories that couldn't be accessed due to API errors
Date parsing is culture-invariant and includes fallback mechanisms to handle various date formats
from the Azure DevOps API.
.LINK
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull-requests/get-pull-requests
https://renovatebot.com/
#>
param(
[Parameter(Mandatory=$false)]
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps organization name")]
[string]$Organization = "effectory",
[Parameter(Mandatory=$false)]
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps project name")]
[string]$Project = "Survey Software",
[Parameter(Mandatory=$true)]
[Parameter(Mandatory=$true, HelpMessage="Azure DevOps Personal Access Token with Code (Read) permissions")]
[string]$PAT,
[Parameter(Mandatory=$false)]
[Parameter(Mandatory=$false, HelpMessage="Output file path for the text report")]
[string]$OutputFile = "RenovatePRs_Stats_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
)
<#
.SYNOPSIS
Outputs a message to both the console and the output file.
.DESCRIPTION
This helper function writes messages to the console with optional color formatting
and simultaneously appends the same message to the output file, unless ConsoleOnly is specified.
.PARAMETER Message
The message to output.
.PARAMETER ForegroundColor
The console text color. Defaults to "White".
.PARAMETER ConsoleOnly
If specified, the message will only be written to the console and not the output file.
Useful for progress messages that shouldn't clutter the report.
#>
function Write-Output-Both {
param (
[string]$Message,
@@ -29,28 +138,34 @@ function Write-Output-Both {
}
# Initialize the output file with a header
Set-Content -Path $OutputFile -Value "Renovate Pull Requests Statistics - $(Get-Date)`n"
# Prepare authentication for Azure DevOps REST API calls
# Personal Access Token must be base64 encoded with a colon prefix
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$PAT"))
$headers = @{
Authorization = "Basic $base64AuthInfo"
}
# Retrieve all repositories from the Azure DevOps project
$reposUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories?api-version=6.0"
$repositories = Invoke-RestMethod -Uri $reposUrl -Method Get -Headers $headers
$repoStats = @()
$reposWithoutRenovate = @()
$disabledRepos = @()
# Initialize arrays to categorize repositories and store statistics
$repoStats = @() # Active repositories with detailed statistics
$reposWithoutRenovate = @() # Active repositories with no Renovate PRs
$disabledRepos = @() # Disabled, locked, or error repositories
# Process each repository in the project
foreach ($repo in $repositories.value) {
$repoName = $repo.name
$repoId = $repo.id
Write-Output-Both "Analyzing repository: $repoName" -ForegroundColor Gray -ConsoleOnly
# Check repository status (disabled, locked, or active)
$isDisabled = $repo.isDisabled -eq $true
$repoDetailsUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$repoId`?api-version=6.0"
try {
@@ -62,10 +177,12 @@ foreach ($repo in $repositories.value) {
$isLocked = $false
}
# Skip analysis for disabled or locked repositories
if ($isDisabled -or $isLocked) {
$status = if ($isDisabled) { "DISABLED" } elseif ($isLocked) { "LOCKED" } else { "UNKNOWN" }
Write-Output-Both " Repository status: $status - Skipping analysis" -ForegroundColor Yellow -ConsoleOnly
# Create entry for disabled/locked repository with N/A values
$disabledRepo = [PSCustomObject]@{
Repository = "$repoName ($status)"
TotalRenovatePRs = "N/A"
@@ -83,23 +200,31 @@ foreach ($repo in $repositories.value) {
continue
}
# Retrieve all pull requests for the current repository
$prsUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$repoId/pullrequests`?api-version=6.0&searchCriteria.status=all"
try {
$pullRequests = Invoke-RestMethod -Uri $prsUrl -Method Get -Headers $headers
# Filter for Renovate PRs based on branch naming pattern
$renovatePRs = $pullRequests.value | Where-Object { $_.sourceRefName -like "refs/heads/renovate/*" }
if ($renovatePRs.Count -gt 0) {
# Categorize Renovate PRs by status
$openPRs = $renovatePRs | Where-Object { $_.status -eq "active" }
$completedPRs = $renovatePRs | Where-Object { $_.status -eq "completed" }
$abandonedPRs = $renovatePRs | Where-Object { $_.status -eq "abandoned" }
# Count PRs in each category
$openCount = $openPRs.Count
$completedCount = $completedPRs.Count
$abandonedCount = $abandonedPRs.Count
# Find the most recent PRs in each category for detailed reporting
$latestOpen = $openPRs | Sort-Object creationDate -Descending | Select-Object -First 1
$latestCompleted = $completedPRs | Sort-Object closedDate -Descending | Select-Object -First 1
$latestCreated = $renovatePRs | Sort-Object creationDate -Descending | Select-Object -First 1
# Extract key information from the latest PRs
$lastCreatedDate = if ($latestCreated) { $latestCreated.creationDate } else { "N/A" }
$lastCompletedDate = if ($latestCompleted) { $latestCompleted.closedDate } else { "N/A" }
$lastCompletedPRTitle = if ($latestCompleted) { $latestCompleted.title } else { "N/A" }
@@ -118,7 +243,20 @@ foreach ($repo in $repositories.value) {
LatestCompletedBranch = $latestCompletedBranch
LastCompletedPRTitle = $lastCompletedPRTitle
RepoStatus = "ACTIVE"
SortDate = if ($lastCreatedDate -eq "N/A") { [DateTime]::MinValue } else { [DateTime]::Parse($lastCreatedDate) }
SortDate = if ($lastCreatedDate -eq "N/A") {
[DateTime]::MinValue
} else {
try {
[DateTime]::Parse($lastCreatedDate, [System.Globalization.CultureInfo]::InvariantCulture)
} catch {
try {
[DateTime]::ParseExact($lastCreatedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture)
} catch {
Write-Output-Both " Warning: Could not parse date '$lastCreatedDate' for repository $repoName" -ForegroundColor Yellow -ConsoleOnly
[DateTime]::MinValue
}
}
}
}
$repoStats += $repoStat
@@ -160,41 +298,71 @@ foreach ($repo in $repositories.value) {
}
}
# Generate and display the main statistics report
Write-Output-Both "`n===== RENOVATE PULL REQUEST STATISTICS BY REPOSITORY (SORTED BY LAST CREATED DATE) =====" -ForegroundColor Green
if ($repoStats.Count -gt 0) {
# Sort repositories by last created date (most recent first)
$sortedStats = $repoStats | Sort-Object -Property SortDate -Descending
# Format the data for display with culture-invariant date parsing
$displayStats = $sortedStats | Select-Object Repository, TotalRenovatePRs, OpenPRs, CompletedPRs, AbandonedPRs,
@{Name="LastCreated"; Expression={
if ($_.LastCreatedDate -eq "N/A" -or $_.LastCreatedDate -eq "Error") { $_.LastCreatedDate }
else { [DateTime]::Parse($_.LastCreatedDate).ToString("yyyy-MM-dd") }
if ($_.LastCreatedDate -eq "N/A" -or $_.LastCreatedDate -eq "Error") {
$_.LastCreatedDate
} else {
try {
[DateTime]::Parse($_.LastCreatedDate, [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
try {
[DateTime]::ParseExact($_.LastCreatedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
$_.LastCreatedDate
}
}
}
}},
@{Name="LastCompleted"; Expression={
if ($_.LastCompletedDate -eq "N/A" -or $_.LastCompletedDate -eq "Error") { $_.LastCompletedDate }
else { [DateTime]::Parse($_.LastCompletedDate).ToString("yyyy-MM-dd") }
if ($_.LastCompletedDate -eq "N/A" -or $_.LastCompletedDate -eq "Error") {
$_.LastCompletedDate
} else {
try {
[DateTime]::Parse($_.LastCompletedDate, [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
try {
[DateTime]::ParseExact($_.LastCompletedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
$_.LastCompletedDate
}
}
}
}},
LatestOpenBranch, LatestCompletedBranch, LastCompletedPRTitle
# Export detailed data to CSV for further analysis
$displayStats | Export-Csv -Path "$($OutputFile).csv" -NoTypeInformation
# Add a note to the original output file
Add-Content -Path $OutputFile -Value "Full data available in: $($OutputFile).csv"
# Add formatted table to the text report and display on console
$statsTable = $displayStats | Format-Table -Property Repository, TotalRenovatePRs, OpenPRs, CompletedPRs, AbandonedPRs, LastCreated, LastCompleted, LatestCompletedBranch, LastCompletedPRTitle | Out-String
Add-Content -Path $OutputFile -Value $statsTable.Trim() # Trim to remove extra whitespace
Add-Content -Path $OutputFile -Value $statsTable.Trim()
$displayStats | Format-Table -AutoSize
# Calculate summary statistics
$totalRepos = $repositories.value.Count
$reposWithRenovate = ($repoStats | Where-Object { $_.TotalRenovatePRs -gt 0 }).Count
$reposDisabledOrLocked = $disabledRepos.Count
$activeRepos = $totalRepos - $reposDisabledOrLocked
# Calculate percentage of active repositories with Renovate PRs
$percentWithRenovate = if ($activeRepos -gt 0) { [math]::Round(($reposWithRenovate / $activeRepos) * 100, 2) } else { 0 }
# Calculate totals across all active repositories
$totalPRs = ($repoStats | Measure-Object -Property TotalRenovatePRs -Sum).Sum
$totalOpenPRs = ($repoStats | Measure-Object -Property OpenPRs -Sum).Sum
$totalCompletedPRs = ($repoStats | Measure-Object -Property CompletedPRs -Sum).Sum
$totalAbandonedPRs = ($repoStats | Measure-Object -Property AbandonedPRs -Sum).Sum
# Display comprehensive summary statistics
Write-Output-Both "`n===== SUMMARY STATISTICS =====" -ForegroundColor Cyan
Write-Output-Both "Total repositories: $totalRepos"
Write-Output-Both "Disabled, locked, or error repositories: $reposDisabledOrLocked"
@@ -206,6 +374,7 @@ if ($repoStats.Count -gt 0) {
Write-Output-Both "Total completed renovate PRs: $totalCompletedPRs"
Write-Output-Both "Total abandoned renovate PRs: $totalAbandonedPRs"
# Display list of repositories that couldn't be analyzed
if ($disabledRepos.Count -gt 0) {
Write-Output-Both "`n===== DISABLED/LOCKED/ERROR REPOSITORIES (NOT INCLUDED IN MAIN REPORT) =====" -ForegroundColor Yellow
$disabledList = $disabledRepos | ForEach-Object { $_.Repository }
@@ -215,4 +384,5 @@ if ($repoStats.Count -gt 0) {
Write-Output-Both "No active repositories found." -ForegroundColor Yellow
}
# Display completion message
Write-Output-Both "`nReport saved to: $OutputFile" -ForegroundColor Cyan