Public/New-PSUADOPullRequest.ps1
|
function New-PSUADOPullRequest { <# .SYNOPSIS Creates a pull request in Azure DevOps using REST API. .DESCRIPTION This function submits a pull request (PR) from a specified source branch to a target branch in a given Azure DevOps repository. It authenticates using a Personal Access Token (PAT) and allows you to provide custom title and description content. You can specify the repository either by Repository ID or Repository Name. .PARAMETER Organization (Optional) The Azure DevOps organization name under which the project resides. Default value is $env:ORGANIZATION. Set using: Set-PSUUserEnvironmentVariable -Name "ORGANIZATION" -Value "your_org_name" .PARAMETER Project (Mandatory) The Azure DevOps project name containing the repository. .PARAMETER RepoId (Mandatory - ParameterSet: ByRepoId) The repository GUID in which to create the pull request. .PARAMETER RepositoryName (Mandatory - ParameterSet: ByRepoName) The repository name in which to create the pull request. .PARAMETER SourceBranch (Optional) The full name of the source branch (e.g., 'refs/heads/feature-branch'). Default value is 'refs/heads/' + current git branch from git branch --show-current. .PARAMETER TargetBranch (Optional) The full name of the target branch (e.g., 'refs/heads/main'). Default value is 'refs/heads/' + default branch from git symbolic-ref refs/remotes/origin/HEAD. .PARAMETER Title (Mandatory) The title of the pull request. .PARAMETER Description (Mandatory) The detailed description of the pull request. .PARAMETER Draft (Optional) Switch parameter to create the pull request as a draft. .PARAMETER CompleteOnApproval (Optional) Switch parameter to enable auto-completion when the pull request is approved. The PR will automatically complete when all required approvals and policies are met. .PARAMETER PAT (Optional) Personal Access Token for Azure DevOps authentication. Default value is $env:PAT. Set using: Set-PSUUserEnvironmentVariable -Name "PAT" -Value "your_pat_token" .EXAMPLE $params = @{ Organization = "omg" Project = "psutilities" RepoId = "12345678-1234-1234-1234-123456789012" SourceBranch = "refs/heads/feature-x" TargetBranch = "refs/heads/main" Title = "Feature X Implementation" Description = "This PR adds feature X." } New-PSUADOPullRequest @params Creates a pull request using repository ID. .EXAMPLE $params = @{ Organization = "omg" Project = "psutilities" RepositoryName = "AzureDevOps" SourceBranch = "refs/heads/feature-branch" TargetBranch = "refs/heads/main" Title = "Bug fix for login" Description = "Fixed authentication issue" } New-PSUADOPullRequest @params Creates a pull request using repository name with specific branches. .EXAMPLE $params = @{ Organization = "omg" Project = "psutilities" RepositoryName = "Ai" Title = "Bug fix for login" Description = "Fixed authentication issue" Draft = $true } New-PSUADOPullRequest @params Creates a draft pull request using repository name with git-detected source/target branches. .EXAMPLE $params = @{ Organization = "omg" Project = "psutilities" RepositoryName = "Core" Title = "Auto-complete feature" Description = "This will auto-complete when approved" CompleteOnApproval = $true } New-PSUADOPullRequest @params Creates a pull request that will automatically complete when all approvals and policies are satisfied. .OUTPUTS [PSCustomObject] .NOTES Author: Lakshmanachari Panuganti Date: 2025-07-30 Updated: 2025-08-14 - Added RepositoryName parameter and parameter sets .LINK https://github.com/lakshmanachari-panuganti/OMG.PSUtilities/tree/main/OMG.PSUtilities.AzureDevOps https://www.linkedin.com/in/lakshmanachari-panuganti/ https://www.powershellgallery.com/packages/OMG.PSUtilities.AzureDevOps https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-requests/create #> [CmdletBinding(DefaultParameterSetName = 'ByRepoName')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingWriteHost', '', Justification = 'This is intended for this function to display formatted output to the user on the console' )] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Title, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Description, [Parameter(Mandatory)] [ValidateScript({ if ($_ -match '^refs/heads/.+') { $true } else { throw "SourceBranch must be in the format 'refs/heads/branch-name'." } })] [string]$SourceBranch, [Parameter(Mandatory)] [ValidateScript({ if ($_ -match '^refs/heads/.+') { $true } else { throw "TargetBranch must be in the format 'refs/heads/branch-name'." } })] [string]$TargetBranch, [Parameter(Mandatory, ParameterSetName = 'ByRepoId')] [ValidateNotNullOrEmpty()] [string]$RepoId, [Parameter(Mandatory, ParameterSetName = 'ByRepoName')] [ValidateNotNullOrEmpty()] [string]$RepositoryName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Project, [Parameter()] [switch]$Draft, [Parameter()] [switch]$CompleteOnApproval, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization = $env:ORGANIZATION, [Parameter()] [ValidateNotNullOrEmpty()] [string]$PAT = $env:PAT ) begin { # Display parameters Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parameters:" foreach ($param in $PSBoundParameters.GetEnumerator()) { if ($param.Key -eq 'PAT') { $maskedPAT = if ($param.Value -and $param.Value.Length -ge 3) { $param.Value.Substring(0, 3) + "********" } else { "***" } Write-Verbose " $($param.Key): $maskedPAT" } else { Write-Verbose " $($param.Key): $($param.Value)" } } # Validate Organization (required because ValidateNotNullOrEmpty doesn't check default values from environment variables) if (-not $Organization) { throw "The default value for the 'ORGANIZATION' environment variable is not set.`nSet it using: Set-PSUUserEnvironmentVariable -Name 'ORGANIZATION' -Value '<org>' or provide via -Organization parameter." } # Validate PAT (required because ValidateNotNullOrEmpty doesn't check default values from environment variables) if (-not $PAT) { throw "The default value for the 'PAT' environment variable is not set.`nSet it using: Set-PSUUserEnvironmentVariable -Name 'PAT' -Value '<pat>' or provide via -PAT parameter." } $headers = Get-PSUAdoAuthHeader -PAT $PAT } process { try { # Validate required parameters that have auto-detection $repositoryIdentifier = $null if ($PSCmdlet.ParameterSetName -eq 'ByRepoId') { $repositoryIdentifier = $RepoId } else { $repos = Get-PSUADORepositories -Project $Project -Organization $Organization -PAT $PAT $matchedRepo = $repos | Where-Object { $_.Name -eq $RepositoryName } if (-not $matchedRepo) { throw "Repository '$RepositoryName' not found in project '$Project'." } $repositoryIdentifier = $matchedRepo.Id } $body = @{ sourceRefName = $SourceBranch targetRefName = $TargetBranch title = $Title description = ($Description -join "`n") isDraft = $Draft.IsPresent } | ConvertTo-Json -Depth 10 $escapedProject = if ($Project -match '%[0-9A-Fa-f]{2}') { $Project } else { [uri]::EscapeDataString($Project) } $uri = "https://dev.azure.com/$Organization/$escapedProject/_apis/git/repositories/$repositoryIdentifier/pullrequests?api-version=7.0" $irmParams = @{ Method = 'Post' Uri = $uri Headers = $headers Body = $body ContentType = 'application/json' ErrorAction = 'Stop' } # Create safe version for verbose output (mask Authorization header) $verboseParams = $irmParams.Clone() if ($verboseParams.Headers -and $verboseParams.Headers['Authorization']) { $verboseParams.Headers = $verboseParams.Headers.Clone() $verboseParams.Headers['Authorization'] = 'Basic ***MASKED***' $verboseParams.Body = $verboseParams.Body | ConvertFrom-Json -Depth 10 } Write-Verbose "Invoke-RestMethod parameters for Pull Request creation: $($verboseParams | Out-String)" $response = Invoke-RestMethod @irmParams -Verbose:$false $WebUrl = "https://dev.azure.com/$Organization/$escapedProject/_git/$repositoryIdentifier/pullrequest/$($response.pullRequestId)" $draftText = if ($response.isDraft) { "Draft " } else { "" } Write-Verbose " ${draftText}Pull Request created successfully. PR ID: $($response.pullRequestId)" Write-Verbose " PR URL: $WebUrl" # Enable auto-completion if specified if ($CompleteOnApproval) { try { # Set auto-complete options $autoCompleteBody = @{ autoCompleteSetBy = @{ id = $response.createdBy.id } completionOptions = @{ mergeStrategy = "noFastForward" deleteSourceBranch = $false squashMerge = $false } } | ConvertTo-Json -Depth 10 $autoCompleteUri = "https://dev.azure.com/$Organization/$escapedProject/_apis/git/repositories/$repositoryIdentifier/pullrequests/$($response.pullRequestId)?api-version=7.0" Write-Verbose " Setting auto-completion for PR ID: $($response.pullRequestId)" # Use parameter splatting for auto-complete PATCH call $autoIRMParams = @{ Method = 'Patch' Uri = $autoCompleteUri Headers = $headers Body = $autoCompleteBody ContentType = 'application/json' ErrorAction = 'Stop' } # Create safe version for verbose output (mask Authorization header) $verboseAutoIRMParams = $autoIRMParams.Clone() if ($verboseAutoIRMParams.Headers -and $verboseAutoIRMParams.Headers['Authorization']) { $verboseAutoIRMParams.Headers = $verboseAutoIRMParams.Headers.Clone() $verboseAutoIRMParams.Headers['Authorization'] = 'Basic ***MASKED***' $verboseAutoIRMParams.Body = $verboseAutoIRMParams.Body | ConvertFrom-Json -Depth 10 } Write-Verbose " Invoke-RestMethod parameters for set auto-complete: $($verboseAutoIRMParams | Out-String)" $response = Invoke-RestMethod @autoIRMParams -Verbose:$false if($response.completionOptions.mergeStrategy -eq "noFastForward") { Write-Verbose " Auto-completion options set: MergeStrategy=$($response.completionOptions.mergeStrategy), DeleteSourceBranch=$($response.completionOptions.deleteSourceBranch), SquashMerge=$($response.completionOptions.squashMerge)" } } catch { Write-Warning "Failed to enable auto-completion: $($_.Exception.Message)" } } [PSCustomObject]@{ Id = $response.pullRequestId Title = $response.title Description = $response.description Status = $response.status IsDraft = $response.isDraft SourceBranch = $response.sourceRefName TargetBranch = $response.targetRefName CreatedBy = $response.createdBy.displayName CreatorEmail = $response.createdBy.uniqueName CreationDate = $response.creationDate RepositoryId = $response.repository.id RepositoryName = $response.repository.name ProjectName = $response.repository.project.name WebUrl = $WebUrl ApiUrl = $response.url CompleteOnApproval = $CompleteOnApproval.IsPresent PSTypeName = 'PSU.ADO.PullRequest' } } catch { $PSCmdlet.ThrowTerminatingError($_) } } } |