diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd7209c..b03240dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # PowerShellForGitHub PowerShell Module ## Changelog +## [0.0.2](https://github.com/PowerShell/PowerShellForGitHub/tree/0.3.0) - (2018/11/13) +### Features: ++ Added support for querying forks and creating new ones. + +### Fixes: +* Will only perform a retry when receiving a `202` response on a `GET` request. Previously, it would + retry regardless of the method of the request. + +More Info: [[pr]](https://github.com/PowerShell/PowerShellForGitHub/pull/41) | [[cl]](https://github.com/PowerShell/PowerHellForGitHub/commit/TODO) + +Author: [**@HowardWolosky**](https://github.com/HowardWolosky) + +------ + ## [0.0.2](https://github.com/PowerShell/PowerShellForGitHub/tree/0.2.0) - (2018/11/13) ### Features: + Significant restructing and refactoring of entire module to make future expansion easier. diff --git a/GitHubCore.ps1 b/GitHubCore.ps1 index 7e881cbf..cd173bd3 100644 --- a/GitHubCore.ps1 +++ b/GitHubCore.ps1 @@ -328,16 +328,23 @@ function Invoke-GHRestMethod if ($result.StatusCode -eq $resultNotReadyStatusCode) { $retryDelaySeconds = Get-GitHubConfiguration -Name RetryDelaySeconds - if ($retryDelaySeconds -gt 0) + + if ($Method -ne 'Get') { - Write-Log -Message "The server has indicated that the result is not yet ready (received status code of [$($result.StatusCode)]). Will retry in [$retryDelaySeconds] seconds." -Level Warning - Start-Sleep -Seconds ($retryDelaySeconds) - return (Invoke-GHRestMethod @PSBoundParameters) + # We only want to do our retry logic for GET requests... + # We don't want to repeat PUT/PATCH/POST/DELETE. + Write-Log -Message "The server has indicated that the result is not yet ready (received status code of [$($result.StatusCode)])." -Level Warning } - else + elseif ($retryDelaySeconds -le 0) { Write-Log -Message "The server has indicated that the result is not yet ready (received status code of [$($result.StatusCode)]), however the module is currently configured to not retry in this scenario (RetryDelaySeconds is set to 0). Please try this command again later." -Level Warning } + else + { + Write-Log -Message "The server has indicated that the result is not yet ready (received status code of [$($result.StatusCode)]). Will retry in [$retryDelaySeconds] seconds." -Level Warning + Start-Sleep -Seconds ($retryDelaySeconds) + return (Invoke-GHRestMethod @PSBoundParameters) + } } if ($ExtendedResult) diff --git a/GitHubRepositoryForks.ps1 b/GitHubRepositoryForks.ps1 new file mode 100644 index 00000000..bfbb3cc4 --- /dev/null +++ b/GitHubRepositoryForks.ps1 @@ -0,0 +1,202 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +function Get-GitHubRepositoryFork +{ +<# + .SYNOPSIS + Gets the list of forks of the specified repository on GitHub. + + .DESCRIPTION + Gets the list of forks of the specified repository on GitHub. + + 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 Sort + The sort order for results. + + .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 + Get-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub + + Gets all of the forks for the PowerShell\PowerShellForGitHub repository. +#> + [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, + + [ValidateSet('newest', 'oldest', 'stargazers')] + [string] $Sort = 'newest', + + [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) + 'Sort' = $Sort + } + + $getParams = @( + "sort=$Sort" + ) + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/forks" + 'Description' = "Getting all forks of $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethodMultipleResult @params +} + +function New-GitHubRepositoryFork +{ +<# + .SYNOPSIS + Creates a new fork of a repository on GitHub. + + .DESCRIPTION + Creates a new fork of a repository on GitHub. + + 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 OrganizationName + Name of the organization that the new repository should be created under. + If not specified, will be created under the current authenticated user's account. + + .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 + New-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub + + Creates a fork of this repository under the current authenticated user's account. + + .EXAMPLE + New-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub -OrganizationName OctoLabs + + Creates a fork of this repository under the OctoLabs organization. +#> + [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] $OrganizationName, + + [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) + } + + $getParams = @() + if ($PSBoundParameters.ContainsKey('OrganizationName') -and + (-not [String]::IsNullOrEmpty($OrganizationName))) + { + $telemetryProperties['OrganizationName'] = Get-PiiSafeString -PlainText $OrganizationName + $getParams += "organization=$OrganizationName" + } + + $params = @{ + 'UriFragment' = "repos/$OwnerName/$RepositoryName/forks`?" + ($getParams -join '&') + 'Method' = 'Post' + 'Description' = "Creating fork of $RepositoryName" + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -BoundParameters $PSBoundParameters -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + $result = Invoke-GHRestMethod @params + + Write-Log -Message 'Forking a repository happens asynchronously. You may have to wait a short period of time (up to 5 minutes) before you can access the git objects.' -Level Warning + return $result +} diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index be669e88..6b692d18 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -7,7 +7,7 @@ CompanyName = 'Microsoft Corporation' Copyright = 'Copyright (C) Microsoft Corporation. All rights reserved.' - ModuleVersion = '0.2.0' + ModuleVersion = '0.3.0' Description = 'PowerShell wrapper for GitHub API' # Script module or binary module file associated with this manifest. @@ -28,6 +28,7 @@ 'GitHubOrganizations.ps1', 'GitHubPullRequests.ps1', 'GitHubRepositories.ps1', + 'GitHubRepositoryForks.ps1', 'GitHubTeams.ps1', 'GitHubUsers.ps1', 'NugetTools.ps1', @@ -56,6 +57,7 @@ 'Get-GitHubRepositoryBranch', 'Get-GitHubRepositoryCollaborator', 'Get-GitHubRepositoryContributor', + 'Get-GitHubRepositoryFork', 'Get-GitHubRepositoryLanguage', 'Get-GitHubRepositoryTag', 'Get-GitHubRepositoryTopic', @@ -72,6 +74,7 @@ 'New-GitHubIssue', 'New-GitHubLabel', 'New-GitHubRepository', + 'New-GitHubRepositoryFork', 'Remove-GitHubLabel', 'Remove-GitHubRepository', 'Reset-GitHubConfiguration', diff --git a/Tests/GitHubRepositoryForks.ps1 b/Tests/GitHubRepositoryForks.ps1 new file mode 100644 index 00000000..2ce839df --- /dev/null +++ b/Tests/GitHubRepositoryForks.ps1 @@ -0,0 +1,115 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubRepositoryForks.ps1 module +#> + +[String] $root = Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path) +. (Join-Path -Path $root -ChildPath 'Tests\Config\Settings.ps1') +Import-Module -Name $root -Force + +function Initialize-AppVeyor +{ +<# + .SYNOPSIS + Configures the tests to run with the authentication information stored in AppVeyor + (if that information exists in the environment). + + .DESCRIPTION + Configures the tests to run with the authentication information stored in AppVeyor + (if that information exists in the environment). + + The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub + + .NOTES + Internal-only helper method. + + The only reason this exists is so that we can leverage CodeAnalysis.SuppressMessageAttribute, + which can only be applied to functions. + + We call this immediately after the declaration so that AppVeyor initialization can heppen + (if applicable). + +#> + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="Needed to configure with the stored, encrypted string value in AppVeyor.")] + param() + + if ($env:AppVeyor) + { + $secureString = $env:avAccessToken | ConvertTo-SecureString -AsPlainText -Force + $cred = New-Object System.Management.Automation.PSCredential "", $secureString + Set-GitHubAuthentication -Credential $cred + + $script:ownerName = $env:avOwnerName + $script:organizationName = $env:avOrganizationName + + $message = @( + 'This run is executed in the AppVeyor environment.', + 'The GitHub Api Token won''t be decrypted in PR runs causing some tests to fail.', + '403 errors possible due to GitHub hourly limit for unauthenticated queries.', + 'Use Set-GitHubAuthentication manually. modify the values in Tests\Config\Settings.ps1,', + 'and run tests on your machine first.') + Write-Warning -Message ($message -join [Environment]::NewLine) + } +} + +Initialize-AppVeyor + +$script:accessTokenConfigured = Test-GitHubAuthenticationConfigured +if (-not $script:accessTokenConfigured) +{ + $message = @( + 'GitHub API Token not defined, some of the tests will be skipped.', + '403 errors possible due to GitHub hourly limit for unauthenticated queries.') + Write-Warning -Message ($message -join [Environment]::NewLine) +} + +# Backup the user's configuration before we begin, and ensure we're at a pure state before running +# the tests. We'll restore it at the end. +$configFile = New-TemporaryFile +Backup-GitHubConfiguration -Path $configFile +Reset-GitHubConfiguration + +Describe 'Creating a new fork for user' { + $originalForks = Get-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub + + Context 'When a new fork is created' { + $repo = New-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub + $newForks = Get-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub -Sort newest + + It 'Should have one more fork than before' { + (@($newForks).Count - @($originalForks).Count) | Should be 1 + } + + It 'Should be the latest fork in the list' { + $newForks[0].full_name | Should be "$($script:ownerName)/PowerShellForGitHub" + } + + Remove-GitHubRepository -OwnerName $script:ownerName -RepositoryName PowerShellForGitHub + } +} + +Describe 'Creating a new fork for an org' { + $originalForks = Get-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub + + Context 'When a new fork is created' { + $repo = New-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub -OrganizationName $script:organizationName + $newForks = Get-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub -Sort newest + + It 'Should have one more fork than before' { + (@($newForks).Count - @($originalForks).Count) | Should be 1 + } + + It 'Should be the latest fork in the list' { + $newForks[0].full_name | Should be "$($script:organizationName)/PowerShellForGitHub" + } + + Remove-GitHubRepository -OwnerName $script:organizationName -RepositoryName PowerShellForGitHub + } +} + +# Restore the user's configuration to its pre-test state +Restore-GitHubConfiguration -Path $configFile diff --git a/USAGE.md b/USAGE.md index cf91b528..064868c6 100644 --- a/USAGE.md +++ b/USAGE.md @@ -22,6 +22,9 @@ * [Updating the current authenticated user](#updating-the-current-authenticated-user) * [Getting any user](#getting-any-user) * [Getting all users](#getting-all-users) + * [Forks](#forks) + * [Get all the forks for a repository](#get-all-the-forks-for-a-repository) + * [Create a new fork](#create-a-new-fork) ---------- @@ -296,3 +299,17 @@ Get-GitHubUser -Name octocat Get-GitHubUser ``` > Warning: This will take a while. It's getting _every_ GitHub user. + +---------- + +### Forks + +#### Get all the forks for a repository +```powershell +Get-GitHubRepositoryFork -OwnerName PowerShell -RepositoryName PowerShellForGitHub +``` + +#### Create a new fork +```powershell +New-GitHubRepositoryForm -OwnerName PowerShell -RepositoryName PowerShellForGitHub +```