From b409e5613a09c0eb297e3fbd60ecd5127df7137a Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Tue, 13 Aug 2024 13:07:53 -0700 Subject: [PATCH] Add support for back porting PRs from GitHub or the Private Azure Repos (#20670) Co-authored-by: Dongbo Wang --------- Co-authored-by: Dongbo Wang --- test/releaseTools/releaseTools.tests.ps1 | 78 +++++++++++ tools/releaseTools.psm1 | 171 +++++++++++++++++++++-- 2 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 test/releaseTools/releaseTools.tests.ps1 diff --git a/test/releaseTools/releaseTools.tests.ps1 b/test/releaseTools/releaseTools.tests.ps1 new file mode 100644 index 0000000000..d27671c3c1 --- /dev/null +++ b/test/releaseTools/releaseTools.tests.ps1 @@ -0,0 +1,78 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + + $testCases = @( + @{ + remoteInfo = 'upstream git@ssh.dev.azure.com:v3/azDoOrg/PowerShellCore/PowerShell (fetch)' + upstreamRemote = 'upstream' + org = 'azDoOrg' + project = 'PowerShellCore' + UpstreamHost = 'git@ssh.dev.azure.com' + RemoteType = 'AzureRepo' + } + @{ + remoteInfo = 'upstream https://azDoOrg.visualstudio.com/PowerShell/_git/PowerShell (fetch)' + upstreamRemote = 'upstream' + org = 'azDoOrg' + project = 'PowerShell' + UpstreamHost = 'azDoOrg.visualstudio.com' + RemoteType = 'AzureRepo' + } + @{ + remoteInfo = 'upstream https://github.com/PowerShell/PowerShell.git (fetch)' + upstreamRemote = 'upstream' + org = 'PowerShell' + project = 'github.com' + UpstreamHost = 'github.com' + RemoteType = 'GitHub' + } + @{ + remoteInfo = 'github https://github.com/PowerShell/PowerShell.git (fetch)' + upstreamRemote = 'github' + org = 'PowerShell' + project = 'github.com' + UpstreamHost = 'github.com' + RemoteType = 'GitHub' + } + @{ + remoteInfo = 'asonetuhasoeu git@github.com:PowerShell/PowerShell.git (fetch)' + upstreamRemote = 'asonetuhasoeu' + org = 'PowerShell' + project = 'github.com' + UpstreamHost = 'github.com' + RemoteType = 'GitHub' + } + + ) +} +Describe "Get-UpstreamInfo" { + BeforeAll { + Import-Module $PSScriptRoot/../../tools/releaseTools.psm1 -force -Verbose + } + It "parses remote Info correctly: " -TestCases $testCases -Test { + param( + [string] + $RemoteInfo, + [string] + $UpstreamRemote, + [string] + $org, + [string] + $Project, + [string] + $UpstreamHost, + [string] + $remoteType + ) + + $upstreamInfo = Get-UpstreamInfo -Upstream $RemoteInfo -UpstreamRemote $UpstreamRemote + $upstreamInfo | Should -Not -BeNullOrEmpty + $upstreamInfo.org | Should -Be $org + $upstreamInfo.project | Should -Be $Project + $upstreamInfo.repo | Should -Be 'PowerShell' + $upstreamInfo.host | Should -Be $UpstreamHost + $upstreamInfo.remoteType | Should -Be $remoteType + } +} diff --git a/tools/releaseTools.psm1 b/tools/releaseTools.psm1 index ec00745ce1..a50651d888 100644 --- a/tools/releaseTools.psm1 +++ b/tools/releaseTools.psm1 @@ -723,6 +723,74 @@ function Get-PRBackportReport { $prs } } +enum RemoteType { + GitHub + AzureRepo +} + +function Get-UpstreamInfo { + param( + [Parameter(Mandatory=$true)] + [string[]]$Upstream, + + [Parameter(Mandatory=$true)] + [string]$UpstreamRemote + ) + + $upstreamName = '(powershell(core)?)(/_git)?/(powershell)' + $pattern = "^$UpstreamRemote\s*(.*)\:(.*/([-\w.]+)/)?$upstreamName(\.git)?.*fetch" + Write-Verbose -Verbose "searching for an upstream with regex: '$pattern'" + $Upstream = $Upstream | Where-Object { $_ -match $pattern } + + Write-Verbose -Verbose "found $Upstream" + + if (!$Upstream) { + throw "Please create an upstream remote that points to $upstreamName" + } + + $matches | Format-Table | Out-String -Stream -Width 9999 | Write-Verbose + $org = $matches[3] + if ($org -ne 'github.com' -and $matches[1] -ne 'git@github.com') { + Write-Verbose 'parsing Azure repo remote' -Verbose + # Azure Repo remote + $project = $matches[4] + $repo = $matches[7] + $upstreamHost = $matches[1] + + if ($upstreamHost -eq 'https') { + $upstreamHost = $org + } + # matches everything but `.` ending in a `.` + # in other word, matching the first part of a hostname. + # like `www.microsoft.com` it would match `www.` with `www` in a capture group. + if ($org -match '([^\..]*)\.') { + $org = $Matches[1] + } + } else { + Write-Verbose 'parsing github remote' -Verbose + # GitHub Repo remote + $org = $matches[4] + $repo = $matches[7] + $upstreamHost = 'github.com' + $project = $upstreamHost + } + + $remoteType = [RemoteType]::GitHub + + if ($upstreamHost -match '.*azure.com$' -or $upstreamHost -match '.*visualstudio.com$') { + [RemoteType] $remoteType = [RemoteType]::AzureRepo + } + + $upstreamMatchInfo = @{ + org = $org + project = $project + repo = $repo + host = $upstreamHost + remoteType = $remoteType + } + + return $upstreamMatchInfo +} # Backports a PR # requires: @@ -748,7 +816,10 @@ function Invoke-PRBackport { $Overwrite, [string] - $BranchPostFix + $BranchPostFix, + + [string] + $UpstreamRemote = 'upstream' ) function script:Invoke-NativeCommand { param( @@ -792,15 +863,14 @@ function Invoke-PRBackport { throw "PR is not merged ($state)" } - $upstream = $null - $upstreamName = 'powershell/powershell' - $upstream = Invoke-NativeCommand { git remote -v } | Where-Object { $_ -match "^upstream.*$upstreamName.*fetch" } + $upstream = Invoke-NativeCommand { git remote -v } + $upstreamMatchInfo = Get-UpstreamInfo -Upstream $upstream -UpstreamRemote $UpstreamRemote + $remoteType = $upstreamMatchInfo.remoteType - if (!$upstream) { - throw "Please create an upstream remote that points to $upstreamName" - } + Write-Verbose -Verbose "remotetype: $remoteType" + $upstreamMatchInfo | Format-Table | Out-String -Stream -Width 9999 | Write-Verbose -Verbose - Invoke-NativeCommand { git fetch upstream $Target } + Invoke-NativeCommand { git fetch $UpstreamRemote $Target } $switch = '-c' if ($Overwrite) { @@ -812,10 +882,22 @@ function Invoke-PRBackport { $branchName += "-$BranchPostFix" } - if ($PSCmdlet.ShouldProcess("Create branch $branchName from upstream/$Target")) { - Invoke-NativeCommand { git switch upstream/$Target $switch $branchName } + if ($PSCmdlet.ShouldProcess("Create branch $branchName from $UpstreamRemote/$Target")) { + Invoke-NativeCommand { git switch $UpstreamRemote/$Target $switch $branchName } } + try { + $revParseParams = @( + '--verify' + "$commitId^{commit}" + ) + Invoke-NativeCommand { git rev-parse --quiet $revParseParams } + } + catch { + throw "Commit does not exist. Try fetching the upstream. (git rev-parse $revParseParams)" + } + + try { Invoke-NativeCommand { git cherry-pick $commitId } } @@ -824,7 +906,72 @@ function Invoke-PRBackport { } if ($PSCmdlet.ShouldProcess("Create the PR")) { - gh pr create --base $Target --title $backportTitle --body "Backport #$PrNumber" --web + $body = "Backport #$PrNumber" + switch($remoteType) { + "AzureRepo" { + Write-Verbose -Verbose "Pushing branch to $UpstreamRemote" + git push --set-upstream $UpstreamRemote HEAD + $parameters = @( + 'repos' + 'pr' + 'create' + ) + # Open in the browser + $parameters += @( + '--open' + ) + $parameters += @( + '--target-branch' + $Target + ) + $parameters += @( + '--title' + $backportTitle + ) + $parameters += @( + '--description' + $body + ) + $parameters += @( + '--squash' + 'true' + ) + $parameters += @( + '--auto-complete' + 'true' + ) + $parameters += @( + '--delete-source-branch' + 'true' + ) + $parameters += @( + '--org' + "https://dev.azure.com/$($upstreamMatchInfo.org)" + ) + $parameters += @( + '--project' + $upstreamMatchInfo.project + ) + $parameters += @( + '--source-branch' + $branchName + ) + $parameters += @( + '--repository' + $upstreamMatchInfo.repo + ) + + Write-Verbose -Verbose "az $parameters" + $null = Invoke-NativeCommand { az $parameters } + } + "GitHub" { + Write-Verbose -Verbose "Creating PR using gh CLI" + gh pr create --base $Target --title $backportTitle --body $body --web + } + default { + throw "unknown remoteType: $remoteType" + } + } } } @@ -848,4 +995,4 @@ function Invoke-PRBackportApproved { } } -Export-ModuleMember -Function Get-ChangeLog, Get-NewOfficalPackage, Update-PsVersionInCode, Get-PRBackportReport, Invoke-PRBackport, Invoke-PRBackportApproved +Export-ModuleMember -Function Get-ChangeLog, Get-NewOfficalPackage, Update-PsVersionInCode, Get-PRBackportReport, Invoke-PRBackport, Invoke-PRBackportApproved, Get-UpstreamInfo