diff --git a/GitHubReferences.ps1 b/GitHubReferences.ps1 new file mode 100644 index 00000000..de8d8348 --- /dev/null +++ b/GitHubReferences.ps1 @@ -0,0 +1,442 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +function Get-GitHubReference +{ +<# + .SYNOPSIS + Retrieve a reference of a given GitHub repository. + + .DESCRIPTION + Retrieve a reference of a given GitHub repository. + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Reference + Name of the reference, for example: "heads/" for branches and "tags/" for tags. + Gets all the references (everything in the namespace, not only heads and tags) if not provided + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .OUTPUTS + [PSCustomObject] + Details of the git reference in the given repository + + .EXAMPLE + Get-GitHubReference -OwnerName Powershell -RepositoryName PowerShellForGitHub -Reference heads/master +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [string] $Reference, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters -DisableValidation + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + 'ProvidedReference' = $PSBoundParameters.ContainsKey('Reference') + } + + if ($OwnerName -xor $RepositoryName) + { + $message = 'You must specify both Owner Name and Repository Name.' + Write-Log -Message $message -Level Error + throw $message + } + + if (-not [String]::IsNullOrEmpty($RepositoryName)) + { + $uriFragment = "repos/$OwnerName/$RepositoryName/git/matching-refs" + $description = "Getting all references for $RepositoryName" + if ($PSBoundParameters.ContainsKey('Reference')) + { + $uriFragment = $uriFragment + "/$Reference" + $description = "Getting Reference $Reference for $RepositoryName" + } + else + { + # Add a slash at the end. Calling it with only matching-refs without the slash causes a 404 + $uriFragment = $uriFragment + "//" + } + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethodMultipleResult @params +} + +function New-GitHubReference +{ + <# + .SYNOPSIS + Create a reference in a given GitHub repository. + + .DESCRIPTION + Create a reference in a given GitHub repository. + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Reference + The name of the reference to be created (eg: heads/master or tags/myTag) + + .PARAMETER Sha + The SHA1 value for the reference to be created + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .OUTPUTS + [PSCustomObject] + Details of the git reference created. Throws an Exception if the reference already exists + + .EXAMPLE + New-GitHubReference -OwnerName Powershell -RepositoryName PowerShellForGitHub -Reference heads/master -Sha aa218f56b14c9653891f9e74264a383fa43fefbd + #> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory)] + [string] $Reference, + + [Parameter(Mandatory)] + [string] $Sha, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters -DisableValidation + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($OwnerName -xor $RepositoryName) + { + $message = 'You must specify both Owner Name and Repository Name.' + Write-Log -Message $message -Level Error + throw $message + } + + $uriFragment = "repos/$OwnerName/$RepositoryName/git/refs" + $description = "Creating Reference $Reference for $RepositoryName from SHA $Sha" + + $hashBody = @{ + 'ref' = "refs/" + $Reference + 'sha' = $Sha + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Method' = 'Post' + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Description' = $description + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} + +function Update-GitHubReference +{ + <# + .SYNOPSIS + Update a reference in a given GitHub repository. + + .DESCRIPTION + Update a reference in a given GitHub repository. + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Reference + The name of the reference to be created (eg: heads/master or tags/myTag) + + .PARAMETER Sha + The updated SHA1 value to be set for this reference + + .PARAMETER Force + Indicates whether to force the update. If not set it will ensure that the update is a fast-forward update. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Update-GitHubReference -OwnerName Powershell -RepositoryName PowerShellForGitHub -Reference heads/master -Sha aa218f56b14c9653891f9e74264a383fa43fefbd + #> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory)] + [string] $Reference, + + [Parameter(Mandatory)] + [string] $Sha, + + [switch] $Force, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters -DisableValidation + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($OwnerName -xor $RepositoryName) + { + $message = 'You must specify both Owner Name and Repository Name.' + Write-Log -Message $message -Level Error + throw $message + } + + $uriFragment = "repos/$OwnerName/$RepositoryName/git/refs/$Reference" + $description = "Updating SHA for Reference $Reference in $RepositoryName to $Sha" + + $hashBody = @{ + 'force' = $Force.IsPresent + 'sha' = $Sha + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Method' = 'Patch' + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Description' = $description + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} + +function Remove-GitHubReference +{ + <# + .SYNOPSIS + Delete a reference in a given GitHub repository. + + .DESCRIPTION + Delete a reference in a given GitHub repository. + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Reference + The name of the reference to be deleted (eg: heads/master) + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Remove-GitHubReference -OwnerName Powershell -RepositoryName PowerShellForGitHub -Reference heads/master + #> + [CmdletBinding( + SupportsShouldProcess, + DefaultParametersetName='Elements')] + [Alias('Delete-GitHubReference')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter( + Mandatory, + ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory)] + [string] $Reference, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog -Invocation $MyInvocation + + $elements = Resolve-RepositoryElements -BoundParameters $PSBoundParameters -DisableValidation + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($OwnerName -xor $RepositoryName) + { + $message = 'You must specify both Owner Name and Repository Name.' + Write-Log -Message $message -Level Error + throw $message + } + + $uriFragment = "repos/$OwnerName/$RepositoryName/git/refs/$Reference" + $description = "Deleting Reference $Reference from repository $RepositoryName" + + $params = @{ + 'UriFragment' = $uriFragment + 'Method' = 'Delete' + 'Body' = (ConvertTo-Json -InputObject $hashBody) + 'Description' = $description + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethod @params +} \ No newline at end of file diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 31c82786..ce257da3 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -32,6 +32,7 @@ 'GitHubMiscellaneous.ps1', 'GitHubOrganizations.ps1', 'GitHubPullRequests.ps1', + 'GitHubReferences.ps1', 'GitHubReleases.ps1', 'GitHubRepositories.ps1', 'GitHubRepositoryForks.ps1', @@ -67,6 +68,7 @@ 'Get-GitHubPathTraffic', 'Get-GitHubPullRequest', 'Get-GitHubRateLimit', + 'Get-GitHubReference', 'Get-GitHubReferrerTraffic', 'Get-GitHubRelease', 'Get-GitHubRepository', @@ -94,6 +96,7 @@ 'New-GitHubLabel', 'New-GitHubMilestone', 'New-GitHubPullRequest', + 'New-GithubReference', 'New-GitHubRepository', 'New-GitHubRepositoryFork', 'Remove-GithubAssignee', @@ -101,6 +104,7 @@ 'Remove-GitHubIssueLabel', 'Remove-GitHubLabel', 'Remove-GitHubMilestone', + 'Remove-GitHubReference', 'Remove-GitHubRepository', 'Rename-GitHubRepository', 'Reset-GitHubConfiguration', @@ -120,6 +124,7 @@ 'Update-GitHubCurrentUser', 'Update-GitHubIssue', 'Update-GitHubLabel', + 'Update-GithubReference', 'Update-GitHubRepository' ) @@ -127,6 +132,7 @@ 'Delete-GitHubComment', 'Delete-GitHubLabel', 'Delete-GitHubMilestone', + 'Delete-GitHubReference', 'Delete-GitHubRepository', 'Get-GitHubBranch', 'Transfer-GitHubRepositoryOwnership' diff --git a/Tests/GitHubReferences.Tests.ps1 b/Tests/GitHubReferences.Tests.ps1 new file mode 100644 index 00000000..b1a32e3d --- /dev/null +++ b/Tests/GitHubReferences.Tests.ps1 @@ -0,0 +1,184 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubReferences.ps1 module +#> + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + if ($accessTokenConfigured) + { + Describe 'Create a new reference(branch) in repository' { + $repositoryName = [Guid]::NewGuid() + $repo = New-GitHubRepository -RepositoryName $repositoryName -AutoInit + $masterRefName = "heads/master" + $existingref = Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $masterRefName + $sha = $existingref.object.sha + + Context 'On creating a valid reference in a new repository from a given SHA' { + $refName = "heads/" + [Guid]::NewGuid().ToString() + $result = New-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $refName -Sha $sha + + It 'Should successfully create the reference' { + $result.ref | Should Be "refs/$refName" + } + } + + Context 'On creating a valid reference in a new repository (specified by Uri) from a given SHA' { + $refName = "heads/" + [Guid]::NewGuid().ToString() + $result = New-GitHubReference -Uri $repo.svn_url -Reference $refName -Sha $sha + + It 'Should successfully create the reference' { + $result.ref | Should Be "refs/$refName" + } + } + + Context 'On creating an existing reference in a new repository from a given SHA' { + It 'Should throw an Exception' { + { New-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $masterRefName -Sha $sha } | Should Throw + } + } + + Context 'On creating an existing reference in a new repository (specified by Uri) from a given SHA' { + It 'Should throw an exception' { + { New-GitHubReference -Uri $repo.svn_url -Reference $masterRefName -Sha $sha } | Should Throw + } + } + + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + } + + Describe 'Getting a reference(branch) from repository' { + $repositoryName = [Guid]::NewGuid() + $repo = New-GitHubRepository -RepositoryName $repositoryName -AutoInit + $masterRefName = "heads/master" + $randomRefName = "heads/$([Guid]::NewGuid())" + + Context 'On getting a valid reference from a new repository' { + $reference = Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $masterRefName + + It 'Should return details of the reference' { + $reference.ref | Should be "refs/$masterRefName" + } + } + + Context 'On getting an invalid reference from a new repository' { + $reference = Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $randomRefName + + It 'Should not return any details' { + $reference | Should be $null + } + } + + Context 'On getting a valid reference using Uri from a new repository' { + $reference = Get-GitHubReference -Uri $repo.svn_url -Reference $masterRefName + + It 'Should return details of the reference' { + $reference.ref | Should be "refs/$masterRefName" + } + } + + Context 'On getting an invalid reference using Uri from a new repository' { + $reference = Get-GitHubReference -Uri $repo.svn_url -Reference $randomRefName + + It 'Should not return any details' { + $reference | Should be $null + } + } + + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + } + + Describe 'Getting all references from repository' { + $repositoryName = [Guid]::NewGuid() + $repo = New-GitHubRepository -RepositoryName $repositoryName -AutoInit + $masterRefName = "heads/master" + $secondRefName = "heads/branch1" + $existingref = Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $masterRefName + $sha = $existingref.object.sha + New-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $secondRefName -Sha $sha + $refNames = @("refs/$masterRefName", "refs/$secondRefName") + + Context 'On getting all references from a new repository' { + $reference = Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName + + It 'Should return all references' { + ($reference.ref | Where-Object {$refNames -Contains $_}).Count | Should be $refNames.Count + } + } + + Context 'On getting all references using Uri from a new repository' { + $reference = Get-GitHubReference -Uri $repo.svn_url + + It 'Should return all references' { + ($reference.ref | Where-Object {$refNames -Contains $_}).Count | Should be $refNames.Count + } + } + + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + } + + Describe 'Delete a reference(branch) from repository' { + $repositoryName = [Guid]::NewGuid() + New-GitHubRepository -RepositoryName $repositoryName -AutoInit + + Context 'On deleting a newly created reference in a new repository' { + $refname = "heads/myRef" + $existingref = Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference "heads/master" + $sha = $existingref.object.sha + New-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $refname -Sha $sha + Remove-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $refname + + It 'Should not return any details when the reference is queried' { + Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $refname | + Should be $null + } + + It 'Should throw an exception when the same reference is deleted again' { + { Remove-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $refname } | + Should Throw + } + } + + Context 'On deleting an invalid reference from a new repository' { + It 'Should throw an exception' { + { Remove-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference "heads/$([Guid]::NewGuid())" } | + Should Throw + } + } + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + } + + Describe 'Update a reference(branch) in repository' { + $repositoryName = [Guid]::NewGuid() + New-GitHubRepository -RepositoryName $repositoryName -AutoInit + $refname = "heads/myRef" + $existingref = Get-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference "heads/master" + $sha = $existingref.object.sha + New-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $refname -Sha $sha + Context 'On updating a newly created reference to a different SHA' { + It 'Should throw an exception if the SHA is invalid' { + { Update-GitHubReference -OwnerName $ownerName -RepositoryName $repositoryName -Reference $refname -Sha "1234" } | + Should Throw + } + } + $null = Remove-GitHubRepository -OwnerName $ownerName -RepositoryName $repositoryName + } + } +} +catch +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +} +