Skip to content

Commit a1f5e93

Browse files
authored
GitHubContents: New Function Set-GitHubContent (#241)
Adds `Set-GitHubContent` An additional parameter `BranchName` has also been added to the `Get-GitHubContent` function which is required by `Set-GitHubContent`. References: [GitHub Rest API v3 Contents](https://developer.github.com/v3/repos/contents/#create-or-update-file-contents)
1 parent d96541e commit a1f5e93

File tree

4 files changed

+570
-2
lines changed

4 files changed

+570
-2
lines changed

GitHubContents.ps1

Lines changed: 348 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
.PARAMETER Path
3131
The file path for which to retrieve contents
3232
33+
.PARAMETER BranchName
34+
The branch, or defaults to the default branch of not specified.
35+
3336
.PARAMETER MediaType
3437
The format in which the API will return the body of the issue.
3538
@@ -126,6 +129,9 @@
126129

127130
[string] $Path,
128131

132+
[ValidateNotNullOrEmpty()]
133+
[string] $BranchName,
134+
129135
[ValidateSet('Raw', 'Html', 'Object')]
130136
[string] $MediaType = 'Object',
131137

@@ -162,6 +168,11 @@
162168
$description = "Getting all content for in $RepositoryName"
163169
}
164170

171+
if ($PSBoundParameters.ContainsKey('BranchName'))
172+
{
173+
$uriFragment += "?ref=$BranchName"
174+
}
175+
165176
$params = @{
166177
'UriFragment' = $uriFragment
167178
'Description' = $description
@@ -197,6 +208,315 @@
197208
return $result
198209
}
199210

211+
filter Set-GitHubContent
212+
{
213+
<#
214+
.SYNOPSIS
215+
Sets the contents of a file or directory in a repository on GitHub.
216+
217+
.DESCRIPTION
218+
Sets the contents of a file or directory in a repository on GitHub.
219+
220+
The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub
221+
222+
.PARAMETER OwnerName
223+
Owner of the repository.
224+
If not supplied here, the DefaultOwnerName configuration property value will be used.
225+
226+
.PARAMETER RepositoryName
227+
Name of the repository.
228+
If not supplied here, the DefaultRepositoryName configuration property value will be used.
229+
230+
.PARAMETER Uri
231+
Uri for the repository.
232+
The OwnerName and RepositoryName will be extracted from here instead of needing to provide
233+
them individually.
234+
235+
.PARAMETER Path
236+
The file path for which to set contents.
237+
238+
.PARAMETER CommitMessage
239+
The Git commit message.
240+
241+
.PARAMETER Content
242+
The new file content.
243+
244+
.PARAMETER Sha
245+
The SHA value of the current file if present. If this parameter is not provided, and the
246+
file currently exists in the specified branch of the repo, it will be read to obtain this
247+
value.
248+
249+
.PARAMETER BranchName
250+
The branch, or defaults to the default branch if not specified.
251+
252+
.PARAMETER CommitterName
253+
The name of the committer of the commit. Defaults to the name of the authenticated user if
254+
not specified. If specified, CommiterEmail must also be specified.
255+
256+
.PARAMETER CommitterEmail
257+
The email of the committer of the commit. Defaults to the email of the authenticated user
258+
if not specified. If specified, CommitterName must also be specified.
259+
260+
.PARAMETER AuthorName
261+
The name of the author of the commit. Defaults to the name of the authenticated user if
262+
not specified. If specified, AuthorEmail must also be specified.
263+
264+
.PARAMETER AuthorEmail
265+
The email of the author of the commit. Defaults to the email of the authenticated user if
266+
not specified. If specified, AuthorName must also be specified.
267+
268+
.PARAMETER AccessToken
269+
If provided, this will be used as the AccessToken for authentication with the
270+
REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated.
271+
272+
.PARAMETER NoStatus
273+
If this switch is specified, long-running commands will run on the main thread
274+
with no commandline status update. When not specified, those commands run in
275+
the background, enabling the command prompt to provide status information.
276+
If not supplied here, the DefaultNoStatus configuration property value will be used.
277+
278+
.INPUTS
279+
GitHub.Branch
280+
GitHub.Content
281+
GitHub.Event
282+
GitHub.Issue
283+
GitHub.IssueComment
284+
GitHub.Label
285+
GitHub.Milestone
286+
GitHub.PullRequest
287+
GitHub.Project
288+
GitHub.ProjectCard
289+
GitHub.ProjectColumn
290+
GitHub.Release
291+
GitHub.Repository
292+
293+
.OUTPUTS
294+
GitHub.Content
295+
296+
.EXAMPLE
297+
Set-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path README.md -CommitMessage 'Adding README.md' -Content '# README' -BranchName master
298+
299+
Sets the contents of the README.md file on the master branch of the PowerShellForGithub repository.
300+
#>
301+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '',
302+
Justification = 'One or more parameters (like NoStatus) are only referenced by helper
303+
methods which get access to it from the stack via Get-Variable -Scope 1.')]
304+
[CmdletBinding(
305+
SupportsShouldProcess,
306+
PositionalBinding = $false)]
307+
[OutputType({$script:GitHubContentTypeName})]
308+
param(
309+
[Parameter(
310+
Mandatory,
311+
ParameterSetName = 'Elements')]
312+
[string] $OwnerName,
313+
314+
[Parameter(
315+
Mandatory,
316+
ParameterSetName = 'Elements')]
317+
[string] $RepositoryName,
318+
319+
[Parameter(
320+
Mandatory,
321+
ValueFromPipelineByPropertyName,
322+
Position = 1,
323+
ParameterSetName='Uri')]
324+
[Alias('RepositoryUrl')]
325+
[string] $Uri,
326+
327+
[Parameter(
328+
Mandatory,
329+
ValueFromPipelineByPropertyName,
330+
Position = 2)]
331+
[string] $Path,
332+
333+
[Parameter(
334+
Mandatory,
335+
Position = 3)]
336+
[string] $CommitMessage,
337+
338+
[Parameter(
339+
Mandatory,
340+
Position = 4)]
341+
[string] $Content,
342+
343+
[Parameter(ValueFromPipelineByPropertyName)]
344+
[string] $Sha,
345+
346+
[Parameter(ValueFromPipelineByPropertyName)]
347+
[string] $BranchName,
348+
349+
[string] $CommitterName,
350+
351+
[string] $CommitterEmail,
352+
353+
[string] $AuthorName,
354+
355+
[string] $AuthorEmail,
356+
357+
[string] $AccessToken,
358+
359+
[switch] $NoStatus
360+
)
361+
362+
$elements = Resolve-RepositoryElements -DisableValidation
363+
$OwnerName = $elements.ownerName
364+
$RepositoryName = $elements.repositoryName
365+
366+
$telemetryProperties = @{
367+
'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName)
368+
'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName)
369+
}
370+
371+
$uriFragment = "/repos/$OwnerName/$RepositoryName/contents/$Path"
372+
373+
$encodedContent = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Content))
374+
375+
$hashBody = @{
376+
message = $CommitMessage
377+
content = $encodedContent
378+
}
379+
380+
if ($PSBoundParameters.ContainsKey('BranchName'))
381+
{
382+
$hashBody['branch'] = $BranchName
383+
}
384+
385+
if ($PSBoundParameters.ContainsKey('CommitterName') -or
386+
$PSBoundParameters.ContainsKey('CommitterEmail'))
387+
{
388+
if (![System.String]::IsNullOrEmpty($CommitterName) -and
389+
![System.String]::IsNullOrEmpty($CommitterEmail))
390+
{
391+
$hashBody['committer'] = @{
392+
name = $CommitterName
393+
email = $CommitterEmail
394+
}
395+
}
396+
else
397+
{
398+
$message = 'Both CommiterName and CommitterEmail need to be specified.'
399+
Write-Log -Message $message -Level Error
400+
throw $message
401+
}
402+
}
403+
404+
if ($PSBoundParameters.ContainsKey('AuthorName') -or
405+
$PSBoundParameters.ContainsKey('AuthorEmail'))
406+
{
407+
if (![System.String]::IsNullOrEmpty($CommitterName) -and
408+
![System.String]::IsNullOrEmpty($CommitterEmail))
409+
{
410+
$hashBody['author'] = @{
411+
name = $AuthorName
412+
email = $AuthorEmail
413+
}
414+
}
415+
else
416+
{
417+
$message = 'Both AuthorName and AuthorEmail need to be specified.'
418+
Write-Log -Message $message -Level Error
419+
throw $message
420+
}
421+
}
422+
423+
if ($PSBoundParameters.ContainsKey('Sha'))
424+
{
425+
$hashBody['sha'] = $Sha
426+
}
427+
428+
if ($PSCmdlet.ShouldProcess(
429+
"$BranchName branch of $RepositoryName",
430+
"Set GitHub Contents on $Path"))
431+
{
432+
Write-InvocationLog
433+
434+
$params = @{
435+
UriFragment = $uriFragment
436+
Description = "Writing content for $Path in the $BranchName branch of $RepositoryName"
437+
Body = (ConvertTo-Json -InputObject $hashBody)
438+
Method = 'Put'
439+
AccessToken = $AccessToken
440+
TelemetryEventName = $MyInvocation.MyCommand.Name
441+
TelemetryProperties = $telemetryProperties
442+
NoStatus = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus `
443+
-ConfigValueName DefaultNoStatus)
444+
}
445+
446+
try
447+
{
448+
return (Invoke-GHRestMethod @params | Add-GitHubContentAdditionalProperties)
449+
}
450+
catch
451+
{
452+
$overwriteShaRequired = $false
453+
454+
# Temporary code to handle current differences in exception object between PS5 and PS7
455+
if ($PSVersionTable.PSedition -eq 'Core')
456+
{
457+
$errorMessage = ($_.ErrorDetails.Message | ConvertFrom-Json).message -replace '\n',' ' -replace '\"','"'
458+
if (($_.Exception -is [Microsoft.PowerShell.Commands.HttpResponseException]) -and
459+
($errorMessage -eq 'Invalid request. "sha" wasn''t supplied.'))
460+
{
461+
$overwriteShaRequired = $true
462+
}
463+
else
464+
{
465+
throw $_
466+
}
467+
}
468+
else
469+
{
470+
$errorMessage = $_.Exception.Message -replace '\n',' ' -replace '\"','"'
471+
if ($errorMessage -like '*Invalid request. "sha" wasn''t supplied.*')
472+
{
473+
$overwriteShaRequired = $true
474+
}
475+
else
476+
{
477+
throw $_
478+
}
479+
}
480+
481+
if ($overwriteShaRequired)
482+
{
483+
# Get SHA from current file
484+
$getGitHubContentParms = @{
485+
Path = $Path
486+
OwnerName = $OwnerName
487+
RepositoryName = $RepositoryName
488+
}
489+
490+
if ($PSBoundParameters.ContainsKey('BranchName'))
491+
{
492+
$getGitHubContentParms['BranchName'] = $BranchName
493+
}
494+
495+
if ($PSBoundParameters.ContainsKey('AccessToken'))
496+
{
497+
$getGitHubContentParms['AccessToken'] = $AccessToken
498+
}
499+
500+
if ($PSBoundParameters.ContainsKey('NoStatus'))
501+
{
502+
$getGitHubContentParms['NoStatus'] = $NoStatus
503+
}
504+
505+
$object = Get-GitHubContent @getGitHubContentParms
506+
507+
$hashBody['sha'] = $object.sha
508+
$params['body'] = ConvertTo-Json -InputObject $hashBody
509+
510+
$message = 'Replacing the content of an existing file requires the current SHA ' +
511+
'of that file. Retrieving the SHA now.'
512+
Write-Log -Level Verbose -Message $message
513+
514+
return (Invoke-GHRestMethod @params | Add-GitHubContentAdditionalProperties)
515+
}
516+
}
517+
}
518+
}
519+
200520
filter Add-GitHubContentAdditionalProperties
201521
{
202522
<#
@@ -235,11 +555,37 @@ filter Add-GitHubContentAdditionalProperties
235555

236556
if (-not (Get-GitHubConfiguration -Name DisablePipelineSupport))
237557
{
238-
$elements = Split-GitHubUri -Uri $item.url
558+
if ($item.html_url)
559+
{
560+
$uri = $item.html_url
561+
}
562+
else
563+
{
564+
$uri = $item.content.html_url
565+
}
566+
567+
$elements = Split-GitHubUri -Uri $uri
239568
$repositoryUrl = Join-GitHubUri @elements
569+
240570
Add-Member -InputObject $item -Name 'RepositoryUrl' -Value $repositoryUrl -MemberType NoteProperty -Force
571+
572+
$hostName = $(Get-GitHubConfiguration -Name 'ApiHostName')
573+
574+
if ($uri -match "^https?://(?:www\.|api\.|)$hostName/(?:[^/]+)/(?:[^/]+)/(?:blob|tree)/([^/]+)/([^#]*)?$")
575+
{
576+
$branchName = $Matches[1]
577+
$path = $Matches[2]
578+
}
579+
else
580+
{
581+
$branchName = [String]::Empty
582+
$path = [String]::Empty
583+
}
584+
585+
Add-Member -InputObject $item -Name 'BranchName' -Value $branchName -MemberType NoteProperty -Force
586+
Add-Member -InputObject $item -Name 'Path' -Value $path -MemberType NoteProperty -Force
241587
}
242588

243589
Write-Output $item
244590
}
245-
}
591+
}

PowerShellForGitHub.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
'Restore-GitHubConfiguration',
131131
'Set-GitHubAuthentication',
132132
'Set-GitHubConfiguration',
133+
'Set-GitHubContent',
133134
'Set-GitHubIssueComment',
134135
'Set-GitHubIssueLabel',
135136
'Set-GitHubLabel',

0 commit comments

Comments
 (0)