1077 lines
45 KiB
PowerShell
1077 lines
45 KiB
PowerShell
Function Set-GiteaConfiguration {
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$giteaURL,
|
|
[Parameter(Mandatory)]
|
|
[string]$token,
|
|
[string]$defaultOwner,
|
|
[string]$defaultRepo,
|
|
[string]$defaultBranch = "main"
|
|
)
|
|
|
|
# Create configuration directory if it doesn't exist
|
|
$configDir = Join-Path -Path $HOME -ChildPath ".giteautils"
|
|
if (-not (Test-Path -Path $configDir)) {
|
|
New-Item -Path $configDir -ItemType Directory | Out-Null
|
|
}
|
|
|
|
# Save configuration
|
|
$config = @{
|
|
giteaURL = $giteaURL
|
|
token = $token
|
|
defaultOwner = $defaultOwner
|
|
defaultRepo = $defaultRepo
|
|
defaultBranch = $defaultBranch
|
|
}
|
|
|
|
$configPath = Join-Path -Path $configDir -ChildPath "config.xml"
|
|
$config | Export-Clixml -Path $configPath -Force
|
|
}
|
|
|
|
Function Get-GiteaConfiguration {
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$LoadVariables,
|
|
[switch]$Force
|
|
)
|
|
|
|
$configPath = Join-Path -Path $HOME -ChildPath ".giteautils/config.xml"
|
|
|
|
if (Test-Path -Path $configPath) {
|
|
$config = Import-Clixml -Path $configPath
|
|
|
|
# If LoadVariables switch is used, set each config value as a variable in the global scope
|
|
if ($LoadVariables) {
|
|
foreach ($key in $config.Keys) {
|
|
# Check if variable exists in global scope
|
|
$variableExists = $false
|
|
try {
|
|
$existingVar = Get-Variable -Name $key -Scope Global -ErrorAction Stop
|
|
$variableExists = $true
|
|
}
|
|
catch {
|
|
$variableExists = $false
|
|
}
|
|
|
|
# Set variable if it doesn't exist or if Force is used
|
|
if (-not $variableExists -or $Force) {
|
|
Write-Verbose "Loading configuration variable: $key = $($config[$key])"
|
|
Set-Variable -Name $key -Value $config[$key] -Scope Global
|
|
Write-Host "Created global variable: `$$key" -ForegroundColor Green
|
|
}
|
|
else {
|
|
Write-Verbose "Skipping existing variable: $key (use -Force to override)"
|
|
}
|
|
}
|
|
}
|
|
|
|
return $config
|
|
}
|
|
else {
|
|
Write-Warning "Gitea configuration not found. Use Set-GiteaConfiguration to set up."
|
|
return $null
|
|
}
|
|
}
|
|
|
|
Function Get-GiteaFileContent {
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieve the content of a file from a Gitea repository using the Gitea API.
|
|
|
|
.DESCRIPTION
|
|
This function retrieves the content of a file from a Gitea repository using the Gitea API. The function requires the URL of the Gitea server, the owner of the repository, the name of the repository, the branch to retrieve the file from, the path to the file, a personal access token, and an optional switch to decode the content of the file from Base64 encoding.
|
|
|
|
.PARAMETER giteaURL
|
|
The URL of the Gitea server.
|
|
|
|
.PARAMETER repoOwner
|
|
The owner of the repository.
|
|
|
|
.PARAMETER repoName
|
|
The name of the repository.
|
|
|
|
.PARAMETER branch
|
|
The branch to retrieve the file from. The default value is 'main'.
|
|
|
|
.PARAMETER filePath
|
|
The path to the file to retrieve. This parameter accepts pipeline input.
|
|
|
|
.PARAMETER token
|
|
A personal access token for the Gitea server.
|
|
|
|
.PARAMETER decode
|
|
A switch parameter to decode the content of the file from Base64 encoding.
|
|
.EXAMPLE
|
|
# Example 1: Retrieve the content of a single file from a Gitea repository
|
|
$file = Get-GiteaFileContent -repoOwner "cityofnorwich" -repoName "software-premierone" -filePath "README.md" -token "your_token"
|
|
|
|
.EXAMPLE
|
|
# Example 2: Retrieve the content of multiple files from a Gitea repository
|
|
$files = Get-GiteaFileContent -repoOwner "cityofnorwich" -repoName "software-premierone" -filePath @("README.md", "Manually Create Installer Package.ps1") -token "your_token"
|
|
|
|
.EXAMPLE
|
|
# Example 3: Retrieve the content of multiple files from a Gitea repository using pipeline input
|
|
$filesArray = @("README.md", "Manually Create Installer Package.ps1") | Get-GiteaFileContent -repoOwner "cityofnorwich" -repoName "software-premierone" -token "your_token"
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$giteaURL = "https://gitea.norwichct.tech",
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$repoOwner,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$repoName,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$branch = "main",
|
|
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
|
|
[Alias('FullName', 'Path')]
|
|
[string[]]$filePath,
|
|
[string]$token,
|
|
[switch]$decode
|
|
)
|
|
|
|
begin {
|
|
# Use configuration if parameters aren't provided
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL') -or
|
|
-not $PSBoundParameters.ContainsKey('repoOwner') -or
|
|
-not $PSBoundParameters.ContainsKey('repoName') -or
|
|
-not $PSBoundParameters.ContainsKey('branch') -or
|
|
-not $PSBoundParameters.ContainsKey('token')) {
|
|
|
|
$config = Get-GiteaConfiguration
|
|
if ($config) {
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL')) { $giteaURL = $config.giteaURL }
|
|
if (-not $PSBoundParameters.ContainsKey('repoOwner')) { $repoOwner = $config.defaultOwner }
|
|
if (-not $PSBoundParameters.ContainsKey('repoName')) { $repoName = $config.defaultRepo }
|
|
if (-not $PSBoundParameters.ContainsKey('branch')) { $branch = $config.defaultBranch }
|
|
if (-not $PSBoundParameters.ContainsKey('token')) { $token = $config.token }
|
|
}
|
|
}
|
|
|
|
# Validate that we have all required parameters
|
|
$missingParams = @()
|
|
if (-not $giteaURL) { $missingParams += "giteaURL" }
|
|
if (-not $repoOwner) { $missingParams += "repoOwner" }
|
|
if (-not $repoName) { $missingParams += "repoName" }
|
|
if (-not $token) { $missingParams += "token" }
|
|
|
|
if ($missingParams.Count -gt 0) {
|
|
throw "Missing required parameters: $($missingParams -join ', '). Either provide them directly or set them with Set-GiteaConfiguration."
|
|
}
|
|
|
|
|
|
$results = @()
|
|
Write-Verbose "Parameters:"
|
|
Write-Verbose "giteaURL: $giteaURL"
|
|
Write-Verbose "repoOwner: $repoOwner"
|
|
Write-Verbose "repoName: $repoName"
|
|
Write-Verbose "branch: $branch"
|
|
Write-Debug "Token: $token"
|
|
Write-Verbose "decode: $decode"
|
|
|
|
$headers = @{
|
|
"Authorization" = "token $token"
|
|
"Accept" = "application/json"
|
|
}
|
|
}
|
|
|
|
process {
|
|
foreach ($file in $filePath) {
|
|
# Normalize path separators for cross-platform compatibility
|
|
$file = $file -replace '\\', '/'
|
|
Write-Verbose "Processing file: $file"
|
|
$encodedFile = [System.Uri]::EscapeDataString($file)
|
|
Write-Verbose "Encoded file: $encodedFile"
|
|
$url = "$giteaURL"
|
|
$url += "/api/v1/repos"
|
|
$url += "/$repoOwner"
|
|
$url += "/$repoName"
|
|
$url += "/contents"
|
|
$url += "/$encodedFile"
|
|
$url += "?ref=$branch"
|
|
Write-Verbose "URL: $url"
|
|
try {
|
|
$fileContent = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
|
|
|
|
$content = if ($decode) {
|
|
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($fileContent.content))
|
|
} else {
|
|
$fileContent.content
|
|
}
|
|
|
|
$results += [PSCustomObject]@{
|
|
Path = $file
|
|
Content = $content
|
|
Size = $fileContent.size
|
|
SHA = $fileContent.sha
|
|
Result = 'Success'
|
|
Error = $null
|
|
}
|
|
}
|
|
catch {
|
|
Write-Error "Failed to retrieve file '$file' from Gitea: $_"
|
|
$results += [PSCustomObject]@{
|
|
Path = $file
|
|
Content = $null
|
|
Size = $null
|
|
SHA = $null
|
|
Result = 'Failure'
|
|
Error = $_.Exception.Message
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
end {
|
|
return $results
|
|
}
|
|
}
|
|
|
|
Function Get-GiteaLFSConfiguration {
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the LFS configuration for a Gitea repository.
|
|
|
|
.DESCRIPTION
|
|
This function retrieves the LFS configuration for a Gitea repository using the Gitea API. The function requires the URL of the Gitea server, the owner of the repository, the name of the repository, and a personal access token.
|
|
The function returns the LFS configuration details.
|
|
#>
|
|
[cmdletbinding()]
|
|
param(
|
|
[string]$giteaURL,
|
|
[string]$repoOwner,
|
|
[string]$repoName,
|
|
[string]$token
|
|
)
|
|
|
|
begin {
|
|
# Use configuration if parameters aren't provided
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL') -or
|
|
-not $PSBoundParameters.ContainsKey('repoOwner') -or
|
|
-not $PSBoundParameters.ContainsKey('repoName') -or
|
|
-not $PSBoundParameters.ContainsKey('token')) {
|
|
|
|
$config = Get-GiteaConfiguration
|
|
if ($config) {
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL')) { $giteaURL = $config.giteaURL }
|
|
if (-not $PSBoundParameters.ContainsKey('repoOwner')) { $repoOwner = $config.defaultOwner }
|
|
if (-not $PSBoundParameters.ContainsKey('repoName')) { $repoName = $config.defaultRepo }
|
|
if (-not $PSBoundParameters.ContainsKey('token')) { $token = $config.token }
|
|
}
|
|
}
|
|
|
|
# Validate that we have all required parameters
|
|
$missingParams = @()
|
|
if (-not $giteaURL) { $missingParams += "giteaURL" }
|
|
if (-not $repoOwner) { $missingParams += "repoOwner" }
|
|
if (-not $repoName) { $missingParams += "repoName" }
|
|
if (-not $token) { $missingParams += "token" }
|
|
|
|
if ($missingParams.Count -gt 0) {
|
|
throw "Missing required parameters: $($missingParams -join ', '). Either provide them directly or set them with Set-GiteaConfiguration."
|
|
}
|
|
}
|
|
|
|
process {
|
|
Write-Verbose "Parameters:"
|
|
Write-Verbose "giteaURL: $giteaURL"
|
|
Write-Verbose "repoOwner: $repoOwner"
|
|
Write-Verbose "repoName: $repoName"
|
|
Write-Debug "Token: $token"
|
|
|
|
$filePath = ".gitattributes"
|
|
|
|
try {
|
|
$LFSConfig = (Get-GiteaFileContent -filePath $filePath -giteaURL $giteaURL -repoOwner $repoOwner -repoName $repoName -branch "main" -token $token -decode -ErrorAction SilentlyContinue).content
|
|
$LFSConfig = $LFSConfig -replace "\r?\n", "`n" # Normalize line endings
|
|
# Get the extensions of files which are tracked by LFS
|
|
# Parse the LFS configuration file and extract extensions
|
|
$lfsExtensions = @()
|
|
foreach ($line in $LFSConfig.Split("`n")) {
|
|
if ($line -match '^\*\.([^\s]+)\s+filter=lfs') {
|
|
$extension = "." + $matches[1]
|
|
$lfsExtensions += $extension
|
|
}
|
|
}
|
|
$lfsExtensions = $lfsExtensions | Sort-Object -Unique
|
|
return $lfsExtensions
|
|
}
|
|
catch {
|
|
Write-Error "Failed to retrieve LFS configuration: $_"
|
|
return @()
|
|
}
|
|
}
|
|
}
|
|
|
|
Function Invoke-GiteaFileDownload {
|
|
<#
|
|
.SYNOPSIS
|
|
Downloads a file from a Gitea repository using a direct download URL.
|
|
|
|
.DESCRIPTION
|
|
This function downloads a file from a Gitea repository using a direct download URL. The function requires the download URL and a personal access token. You can optionally specify an output path and use the force switch to overwrite existing files.
|
|
|
|
.PARAMETER giteaURL
|
|
The URL of the Gitea server.
|
|
|
|
.PARAMETER downloadURL
|
|
The direct download URL for the file from the Gitea server.
|
|
|
|
.PARAMETER token
|
|
A personal access token for the Gitea server.
|
|
|
|
.PARAMETER outputPath
|
|
The path where the downloaded file should be saved. If not specified, the file will be saved in the current directory with its original name from the download URL.
|
|
If using PreserveRelativePath, outputPath is treated as the base directory to append the relative path to.
|
|
|
|
.PARAMETER outputName
|
|
The name of the file to save the downloaded content as. If not specified, the file will be saved with its original name from the download URL.
|
|
Cannot be used with PreserveRelativePath.
|
|
|
|
.PARAMETER PreserveRelativePath
|
|
A switch parameter to preserve the relative path structure from the repository.
|
|
If used without specifying an outputPath, creates the directory structure in the current location.
|
|
If used with outputPath, treats the outputPath as the base directory to append the relative path to.
|
|
Cannot be used with outputName.
|
|
|
|
.PARAMETER OverwriteByHash
|
|
A switch parameter to overwrite the file if it exists based on its hash.
|
|
|
|
.PARAMETER force
|
|
A switch parameter to force overwriting of an existing file at the output path.
|
|
|
|
.PARAMETER type
|
|
The type of the item to download (file, dir, lfs). This parameter is typically used with pipeline input from Get-GiteaChildItem.
|
|
Directories will be skipped with a verbose message.
|
|
|
|
.PARAMETER sha
|
|
The SHA of the file to download. This parameter is typically used with pipeline input from Get-GiteaChildItem.
|
|
Can be combined with OverwriteByHash to overwrite an existing file if the hashes do not match.
|
|
|
|
.EXAMPLE
|
|
# Example 1: Download a file to the current directory
|
|
Invoke-GiteaFileDownload -downloadURL "https://gitea.example.com/api/v1/repos/owner/repo/raw/path/to/file.txt" -token "your_token"
|
|
|
|
.EXAMPLE
|
|
# Example 2: Download a file to a specific location with force overwrite
|
|
Invoke-GiteaFileDownload -downloadURL "https://gitea.example.com/api/v1/repos/owner/repo/raw/path/to/file.txt" -token "your_token" -outputPath "C:\Downloads\" -outputName "file.txt" -force
|
|
|
|
.EXAMPLE
|
|
# Example 3: Download files in pipeline from Get-GiteaChildItem
|
|
Get-GiteaChildItem -path "docs" | Where-Object { $_.type -eq 'file' } | Invoke-GiteaFileDownload
|
|
|
|
.EXAMPLE
|
|
# Example 4: Download files preserving their relative paths
|
|
Get-GiteaChildItem -path "docs" -Recurse | Where-Object { $_.type -eq 'file' } | Invoke-GiteaFileDownload -PreserveRelativePath
|
|
|
|
.EXAMPLE
|
|
# Example 5: Download files preserving their relative paths with a base output directory
|
|
Get-GiteaChildItem -path "docs" -Recurse | Where-Object { $_.type -eq 'file' } | Invoke-GiteaFileDownload -PreserveRelativePath -outputPath "C:\Downloads"
|
|
#>
|
|
|
|
[cmdletbinding()]
|
|
param(
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$downloadURL,
|
|
[string]$token,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$outputPath,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$outputName,
|
|
[switch]$PreserveRelativePath,
|
|
[switch]$OverwriteByHash,
|
|
[switch]$force,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$type,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$sha
|
|
)
|
|
|
|
begin {
|
|
# Use configuration if parameters aren't provided
|
|
if (-not $PSBoundParameters.ContainsKey('token')) {
|
|
|
|
$config = Get-GiteaConfiguration
|
|
if ($config) {
|
|
if (-not $PSBoundParameters.ContainsKey('token')) { $token = $config.token }
|
|
}
|
|
}
|
|
|
|
# Validate that we have the required token
|
|
if (-not $token) {
|
|
throw "Missing token parameter. Either provide it directly or set it with Set-GiteaConfiguration."
|
|
}
|
|
|
|
Write-Verbose "Parameters:"
|
|
Write-Debug "Token: $token (length: $(if($token){$token.Length}else{0}))"
|
|
Write-Verbose "PreserveRelativePath: $PreserveRelativePath"
|
|
|
|
# Ensure PreserveRelativePath is not used with outputName
|
|
if ($PreserveRelativePath -and $PSBoundParameters.ContainsKey('outputName')) {
|
|
throw "The -PreserveRelativePath switch cannot be used with the -outputName parameter. Use -outputPath instead."
|
|
}
|
|
|
|
# Create a WebClient to be reused
|
|
$webClient = New-Object System.Net.WebClient
|
|
$webClient.Headers.Add("Authorization", "token $token")
|
|
}
|
|
|
|
process {
|
|
# If type is not provided, check for any hints for cases such as LFS where a type can be inferred and special handling is needed
|
|
If($downloadURL -match "/api/v1/repos/[^/]+/[^/]+/media/") {
|
|
$type = "lfs"
|
|
}
|
|
|
|
Write-Verbose "Type: $type"
|
|
# Handle the type parameter
|
|
|
|
# Skip directories and provide a verbose message
|
|
if ($type -eq "dir") {
|
|
Write-Verbose "Skipping directory"
|
|
return [PSCustomObject]@{
|
|
SourceURL = $downloadURL
|
|
Type = $type
|
|
Result = 'Skipped'
|
|
Error = $null
|
|
Timestamp = Get-Date
|
|
FileSize = $null
|
|
}
|
|
}
|
|
|
|
# Handle invalid entry types
|
|
if ([string]::IsNullOrEmpty($type) -and [string]::IsNullOrEmpty($downloadURL)) {
|
|
$errorMsg = "Invalid item type or missing download URL for path"
|
|
Write-Error $errorMsg
|
|
return [PSCustomObject]@{
|
|
SourceURL = $downloadURL
|
|
Type = $type
|
|
Result = 'Failure'
|
|
Error = $errorMsg
|
|
Timestamp = Get-Date
|
|
FileSize = $null
|
|
}
|
|
}
|
|
|
|
|
|
# Parse the download URL to extract the file path and file name
|
|
|
|
# Separate the URL into its components: Gitea URL, Repo Owner, Repo Name, branch, file path, and file name
|
|
$uri = New-Object System.Uri($downloadURL)
|
|
$pathSegments = $uri.AbsolutePath.Trim('/').Split('/')
|
|
If($type -eq "lfs") {
|
|
# Everything after the media segment is considered the file path with the last segment being the file name
|
|
$branchIndex = [Array]::IndexOf($pathSegments, "media") + 1
|
|
$DownloadFilePath = $pathSegments[$branchIndex..($pathSegments.Length - 2)] -join '/'
|
|
$DownloadFileName = $pathSegments[-1]
|
|
}
|
|
Else {
|
|
# Everything after the branch + 1 segment is considered the file path with the last segment being the file name
|
|
$branchIndex = [Array]::IndexOf($pathSegments, "branch") + 2
|
|
$DownloadFilePath = $pathSegments[$branchIndex..($pathSegments.Length - 2)] -join '/'
|
|
# Replace any URL encoded characters in the file name
|
|
$DownloadFileName = $pathSegments[-1]
|
|
}
|
|
|
|
$DownloadFilePath = [System.Uri]::UnescapeDataString($DownloadFilePath)
|
|
|
|
if ($PreserveRelativePath -and $outputPath) {
|
|
# If PreserveRelativePath is used, set up the directory structure
|
|
Write-Verbose "Preserving relative path structure for: $DownloadFileName"
|
|
|
|
$baseDir = $outputPath
|
|
Write-Verbose "Using base directory: $baseDir"
|
|
|
|
# Combine the base directory with the file path
|
|
$fileOutputPath = Join-Path -Path $baseDir -ChildPath $DownloadFilePath
|
|
# Combine with the file name
|
|
$fileOutputPath = Join-Path -Path $fileOutputPath -ChildPath $DownloadFileName
|
|
}
|
|
elseif($PreserveRelativePath) {
|
|
# If no outputPath is specified, use the current directory as base and the file name from the download URL
|
|
$fileOutputPath = Join-Path -Path (Get-Location) -ChildPath $DownloadFilePath
|
|
# Combine with the file name
|
|
$fileOutputPath = Join-Path -Path $fileOutputPath -ChildPath $DownloadFileName
|
|
}
|
|
elseif ($outputPath -and $outputName) {
|
|
# If both outputPath and outputName are specified, use outputPath as the base directory with the file name specified by outputName
|
|
$fileOutputPath = Join-Path -Path $outputPath -ChildPath $outputName
|
|
}
|
|
elseif ($outputPath) {
|
|
# If outputPath is specified, use it as the base directory with the file name from the download URL
|
|
$fileOutputPath = Join-Path -Path $outputPath -ChildPath $DownloadFileName
|
|
}
|
|
elseif($outputName) {
|
|
# If only outputName is specified, use the current directory with the specified outputName
|
|
$fileOutputPath = Join-Path -Path (Get-Location) -ChildPath $outputName
|
|
}
|
|
else {
|
|
# Fallback to current directory with the file name from the download URL
|
|
$fileOutputPath = Join-Path -Path (Get-Location) -ChildPath $DownloadFileName
|
|
}
|
|
|
|
# Normalize path separators
|
|
$fileOutputPath = $fileOutputPath.Replace("/", [System.IO.Path]::DirectorySeparatorChar)
|
|
|
|
|
|
Write-Verbose "Output path: $fileOutputPath"
|
|
|
|
$result = [PSCustomObject]@{
|
|
Path = $fileOutputPath
|
|
SourceURL = $downloadURL
|
|
Type = "file"
|
|
Result = $null
|
|
Error = $null
|
|
Timestamp = Get-Date
|
|
FileSize = $null
|
|
}
|
|
|
|
if((Test-Path -Path $fileOutputPath -PathType Leaf) -and (-not $force)) {
|
|
# Check if OverwriteByHash is set
|
|
if ($OverwriteByHash) {
|
|
Write-Verbose "OverwriteByHash is set; checking file hash for existing file: $fileOutputPath"
|
|
$existingFileHash = (Get-FileHash -Path $fileOutputPath -Algorithm SHA256).Hash
|
|
$downloadFileHash = $sha
|
|
|
|
if ($existingFileHash -eq $downloadFileHash) {
|
|
Write-Host "The file '$fileOutputPath' already exists and hashes match. Skipping download."
|
|
$result.Result = 'Skipped'
|
|
return $result
|
|
}
|
|
else {
|
|
Write-Host "The file '$fileOutputPath' already exists but hashes do not match and OverwriteByHash is set; overwriting file."
|
|
|
|
}
|
|
}
|
|
else {
|
|
$result.Result = 'Skipped'
|
|
$errorMsg = "The file '$fileOutputPath' already exists. Use the -Force switch to overwrite the file."
|
|
Write-Error $errorMsg
|
|
$result.Error = $errorMsg
|
|
return $result
|
|
}
|
|
}
|
|
|
|
try {
|
|
# Create the directory structure if it doesn't exist
|
|
$directory = Split-Path -Path $fileOutputPath -Parent
|
|
if (-not (Test-Path -Path $directory -PathType Container) -and $directory) {
|
|
Write-Verbose "Creating directory structure: $directory"
|
|
New-Item -Path $directory -ItemType Directory -Force | Out-Null
|
|
}
|
|
|
|
# Download the file
|
|
Write-Verbose "Downloading from $downloadURL to $fileOutputPath"
|
|
|
|
# Use synchronous download
|
|
$webClient.DownloadFile($downloadURL, $fileOutputPath)
|
|
|
|
Write-Verbose "File downloaded successfully to $fileOutputPath"
|
|
|
|
# Get file info for the result object
|
|
if (Test-Path -Path $fileOutputPath -PathType Leaf) {
|
|
$fileInfo = Get-Item -Path $fileOutputPath
|
|
$result.FileSize = $fileInfo.Length
|
|
}
|
|
|
|
$result.Result = 'Success'
|
|
return $result
|
|
}
|
|
catch {
|
|
$result.Result = 'Failure'
|
|
$errorMsg = "Failed to download file from Gitea: $_"
|
|
Write-Error $errorMsg
|
|
$result.Error = $errorMsg
|
|
return $result
|
|
}
|
|
}
|
|
|
|
end {
|
|
# Clean up resources
|
|
if ($webClient) {
|
|
$webClient.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
Function Get-GiteaChildItem {
|
|
<#
|
|
.SYNOPSIS
|
|
Lists files and directories in a Gitea repository path.
|
|
|
|
.DESCRIPTION
|
|
This function retrieves a list of files and directories from a specified path in a Gitea repository.
|
|
The results can be directly piped to Get-GiteaFileContent to retrieve file contents.
|
|
|
|
.PARAMETER giteaURL
|
|
The URL of the Gitea server.
|
|
|
|
.PARAMETER repoOwner
|
|
The owner of the repository.
|
|
|
|
.PARAMETER repoName
|
|
The name of the repository.
|
|
|
|
.PARAMETER Path
|
|
The path to list items from. This parameter accepts pipeline input.
|
|
|
|
.PARAMETER branch
|
|
The branch to retrieve the items from. The default value is 'main'.
|
|
|
|
.PARAMETER token
|
|
A personal access token for the Gitea server.
|
|
|
|
.PARAMETER Recurse
|
|
Recursively lists items in all subdirectories.
|
|
|
|
.PARAMETER Depth
|
|
Specifies the number of subdirectory levels to include in the recursion. Default is unlimited if Recurse is specified.
|
|
|
|
.PARAMETER File
|
|
Returns only files, omitting directories from the results.
|
|
|
|
.EXAMPLE
|
|
# List items in the root directory of a repository
|
|
Get-GiteaChildItem -repoOwner "owner" -repoName "repo" -Path "" -token "your_token"
|
|
|
|
.EXAMPLE
|
|
# List items and pipe files to Get-GiteaFileContent to get their content
|
|
Get-GiteaChildItem -repoOwner "owner" -repoName "repo" -Path "docs" -token "your_token" |
|
|
Where-Object { $_.type -eq "file" } |
|
|
Get-GiteaFileContent -token "your_token" -decode
|
|
|
|
.EXAMPLE
|
|
# Recursively list all items in a repository
|
|
Get-GiteaChildItem -repoOwner "owner" -repoName "repo" -Path "src" -Recurse -token "your_token"
|
|
|
|
.EXAMPLE
|
|
# List items with a maximum depth of 2 subdirectories
|
|
Get-GiteaChildItem -repoOwner "owner" -repoName "repo" -Path "src" -Recurse -Depth 2 -token "your_token"
|
|
|
|
.EXAMPLE
|
|
# List only files (no directories) in a repository
|
|
Get-GiteaChildItem -repoOwner "owner" -repoName "repo" -Path "src" -File -token "your_token"
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$giteaURL,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$repoOwner,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$repoName,
|
|
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
|
|
[Alias('FullName')]
|
|
[string[]]$Path,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$branch = "main",
|
|
[string]$token,
|
|
[switch]$Recurse,
|
|
[int]$Depth,
|
|
[switch]$File
|
|
)
|
|
|
|
begin {
|
|
# Initialize results array
|
|
$results = @()
|
|
|
|
# Use configuration if parameters aren't provided
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL') -or
|
|
-not $PSBoundParameters.ContainsKey('repoOwner') -or
|
|
-not $PSBoundParameters.ContainsKey('repoName') -or
|
|
-not $PSBoundParameters.ContainsKey('branch') -or
|
|
-not $PSBoundParameters.ContainsKey('token')) {
|
|
|
|
$config = Get-GiteaConfiguration
|
|
if ($config) {
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL')) { $giteaURL = $config.giteaURL }
|
|
if (-not $PSBoundParameters.ContainsKey('repoOwner')) { $repoOwner = $config.defaultOwner }
|
|
if (-not $PSBoundParameters.ContainsKey('repoName')) { $repoName = $config.defaultRepo }
|
|
if (-not $PSBoundParameters.ContainsKey('branch')) { $branch = $config.defaultBranch }
|
|
if (-not $PSBoundParameters.ContainsKey('token')) { $token = $config.token }
|
|
}
|
|
}
|
|
|
|
# Validate that we have all required parameters
|
|
$missingParams = @()
|
|
if (-not $giteaURL) { $missingParams += "giteaURL" }
|
|
if (-not $repoOwner) { $missingParams += "repoOwner" }
|
|
if (-not $repoName) { $missingParams += "repoName" }
|
|
if (-not $token) { $missingParams += "token" }
|
|
|
|
if ($missingParams.Count -gt 0) {
|
|
throw "Missing required parameters: $($missingParams -join ', '). Either provide them directly or set them with Set-GiteaConfiguration."
|
|
}
|
|
|
|
Write-Verbose "Parameters:"
|
|
Write-Verbose "giteaURL: $giteaURL"
|
|
Write-Verbose "repoOwner: $repoOwner"
|
|
Write-Verbose "repoName: $repoName"
|
|
Write-Verbose "Path: $Path"
|
|
Write-Debug "Token: $token"
|
|
Write-Verbose "File: $File"
|
|
|
|
$headers = @{
|
|
"Authorization" = "token $token"
|
|
"Accept" = "application/json"
|
|
}
|
|
|
|
# Load the GIT LFS configuration if needed
|
|
$lfsExtensions = Get-GiteaLFSConfiguration -giteaURL $giteaURL -repoOwner $repoOwner -repoName $repoName -token $token
|
|
|
|
# Initialize a queue for directories to process when using recursion
|
|
$directoryQueue = [System.Collections.Queue]::new()
|
|
}
|
|
|
|
process {
|
|
$paths = $path
|
|
foreach ($path in $paths) {
|
|
Write-Verbose "Processing path: $path"
|
|
# Normalize path separators for cross-platform compatibility
|
|
$normalizedPath = $path -replace '\\', '/' -replace '/$', ''
|
|
Write-Verbose "Normalized path: $normalizedPath"
|
|
$encodedPath = [System.Uri]::EscapeDataString($normalizedPath)
|
|
$url = "$giteaURL"
|
|
$url += "/api/v1/repos"
|
|
$url += "/$repoOwner"
|
|
$url += "/$repoName"
|
|
$url += "/contents"
|
|
# Only add the path component if it's not empty
|
|
if (-not [string]::IsNullOrWhiteSpace($normalizedPath)) {
|
|
$url += "/$encodedPath"
|
|
}
|
|
$url += "?ref=$branch"
|
|
Write-Verbose "URL: $url"
|
|
|
|
try {
|
|
$response = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
|
|
|
|
# Handle both single items and arrays
|
|
$items = if ($response -is [array]) { $response } else { @($response) }
|
|
|
|
foreach ($item in $items) {
|
|
# Check if the name ends with any of the LFS extensions
|
|
$isLFS = $false
|
|
foreach ($ext in $lfsExtensions) {
|
|
if ($item.name.EndsWith($ext, [System.StringComparison]::InvariantCultureIgnoreCase)) {
|
|
$isLFS = $true
|
|
break
|
|
}
|
|
}
|
|
if ($isLFS) {
|
|
$item.type = "lfs"
|
|
# Save the original download URL for LFS pointers to be used to populate the size and sha fields
|
|
$GitLFSPointerURL = $item.download_url
|
|
# Use the Gitea API to get the LFS pointer details
|
|
# For LFS files, we need to get the pointer file and parse it for details
|
|
$lfsPointerContent = Invoke-RestMethod -Uri $GitLFSPointerURL -Method Get -Headers $headers -ErrorAction Stop
|
|
Write-Debug "LFS Pointer Content: $($lfsPointerContent)"
|
|
|
|
# Parse the LFS pointer to extract size and SHA
|
|
if ($lfsPointerContent -match 'oid sha256:([a-f0-9]+)') {
|
|
$item.sha = $matches[1]
|
|
}
|
|
if ($lfsPointerContent -match 'size (\d+)') {
|
|
$item.size = [long]$matches[1]
|
|
}
|
|
# Set the download URL to the media endpoint for LFS files to download the actual file
|
|
$item.download_url = "$giteaURL/api/v1/repos/$repoOwner/$repoName/media/$($item.path)?ref=$branch"
|
|
}
|
|
|
|
$itemObj = [PSCustomObject]@{
|
|
name = $item.name
|
|
Path = $item.path
|
|
type = $item.type
|
|
size = $item.size
|
|
sha = $item.sha
|
|
downloadURL = $item.download_url
|
|
Result = 'Success'
|
|
Error = $null
|
|
Level = 0
|
|
}
|
|
|
|
$results += $itemObj
|
|
|
|
# If the item is a directory and we're recursing, add it to the queue
|
|
if ($Recurse -and $item.type -eq 'dir') {
|
|
$directoryQueue.Enqueue(@{
|
|
Path = $item.path
|
|
Level = 1
|
|
})
|
|
}
|
|
}
|
|
|
|
# Process the directory queue if recursion is enabled
|
|
if ($Recurse) {
|
|
while ($directoryQueue.Count -gt 0) {
|
|
$currentDir = $directoryQueue.Dequeue()
|
|
$currentPath = $currentDir.Path
|
|
$currentLevel = $currentDir.Level
|
|
|
|
# Check depth limit if specified
|
|
if ($PSBoundParameters.ContainsKey('Depth') -and $currentLevel -gt $Depth) {
|
|
continue
|
|
}
|
|
|
|
Write-Verbose "Recursing into: $currentPath (Level: $currentLevel)"
|
|
|
|
# Normalize the path format for consistent handling
|
|
$normalizedSubPath = $currentPath -replace '\\', '/' -replace '/$', ''
|
|
$encodedSubPath = [System.Uri]::EscapeDataString($normalizedSubPath)
|
|
|
|
Write-Verbose "Normalized sub path: $normalizedSubPath"
|
|
Write-Verbose "Encoded sub path: $encodedSubPath"
|
|
|
|
# Fix the URL construction for subdirectories
|
|
$subUrl = "$giteaURL/api/v1/repos/$repoOwner/$repoName/contents/$encodedSubPath"
|
|
# Add the branch reference separately to avoid issues
|
|
$subUrl += "?ref=$branch"
|
|
Write-Verbose "Sub URL: $subUrl"
|
|
|
|
try {
|
|
$subResponse = Invoke-RestMethod -Uri $subUrl -Method Get -Headers $headers -ErrorAction Stop
|
|
$subItems = if ($subResponse -is [array]) { $subResponse } else { @($subResponse) }
|
|
|
|
foreach ($subItem in $subItems) {
|
|
# Check if the name ends with any of the LFS extensions
|
|
$isLFS = $false
|
|
foreach ($ext in $lfsExtensions) {
|
|
if ($subItem.name.EndsWith($ext, [System.StringComparison]::InvariantCultureIgnoreCase)) {
|
|
$isLFS = $true
|
|
break
|
|
}
|
|
}
|
|
if ($isLFS) {
|
|
$subItem.type = "lfs"
|
|
# Save the original download URL for LFS pointers to be used to populate the size and sha fields
|
|
$GitLFSPointerURL = $subItem.download_url
|
|
# Use the Gitea API to get the LFS pointer details
|
|
# For LFS files, we need to get the pointer file and parse it for details
|
|
$lfsPointerContent = Invoke-RestMethod -Uri $GitLFSPointerURL -Method Get -Headers $headers -ErrorAction Stop
|
|
Write-Debug "LFS Pointer Content: $($lfsPointerContent)"
|
|
|
|
# Parse the LFS pointer to extract size and SHA
|
|
if ($lfsPointerContent -match 'oid sha256:([a-f0-9]+)') {
|
|
$subItem.sha = $matches[1]
|
|
}
|
|
if ($lfsPointerContent -match 'size (\d+)') {
|
|
$subItem.size = [long]$matches[1]
|
|
}
|
|
# Set the download URL to the media endpoint for LFS files to download the actual file
|
|
$subItem.download_url = "$giteaURL/api/v1/repos/$repoOwner/$repoName/media/$($subItem.path)"
|
|
}
|
|
|
|
$subItemObj = [PSCustomObject]@{
|
|
name = $subItem.name
|
|
Path = $subItem.path
|
|
type = $subItem.type
|
|
size = $subItem.size
|
|
sha = $subItem.sha
|
|
downloadURL = $subItem.download_url
|
|
Result = 'Success'
|
|
Error = $null
|
|
Level = $currentLevel
|
|
}
|
|
|
|
$results += $subItemObj
|
|
|
|
# If the subitem is a directory, add it to the queue for further processing
|
|
if ($subItem.type -eq 'dir') {
|
|
$nextLevel = $currentLevel + 1
|
|
# Only add to queue if we haven't hit our depth limit
|
|
if (-not $PSBoundParameters.ContainsKey('Depth') -or $nextLevel -le $Depth) {
|
|
$directoryQueue.Enqueue(@{
|
|
Path = $subItem.path
|
|
Level = $nextLevel
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
$errorDetails = if ($_.ErrorDetails) {
|
|
try { $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue } catch { $_.ErrorDetails.Message }
|
|
} else { $null }
|
|
|
|
Write-Error "Failed to recursively retrieve path '$normalizedSubPath' from Gitea: $($_.Exception.Message)`n$($errorDetails | ConvertTo-Json -Depth 1 -Compress)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
$errorDetails = if ($_.ErrorDetails) {
|
|
try { $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue } catch { $_.ErrorDetails.Message }
|
|
} else { $null }
|
|
|
|
Write-Error "Failed to retrieve '$normalizedPath' from Gitea: $($_.Exception.Message)`n$($errorDetails | ConvertTo-Json -Depth 1 -Compress)"
|
|
$results += [PSCustomObject]@{
|
|
Path = $normalizedPath
|
|
repoOwner = $repoOwner
|
|
repoName = $repoName
|
|
giteaURL = $giteaURL
|
|
branch = $branch
|
|
type = $null
|
|
name = $null
|
|
size = $null
|
|
sha = $null
|
|
downloadURL = $null
|
|
Result = 'Failure'
|
|
Error = $_.Exception.Message
|
|
Level = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
end {
|
|
# Filter out directories if -File switch is specified
|
|
if ($File) {
|
|
Write-Verbose "Filtering results to include only files"
|
|
$results = $results | Where-Object { $_.type -ne 'dir' }
|
|
}
|
|
|
|
return $results
|
|
}
|
|
}
|
|
|
|
function Get-GiteaReleases {
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the releases for a Gitea repository.
|
|
|
|
.DESCRIPTION
|
|
This function retrieves the releases for a Gitea repository using the Gitea API. The function requires the URL of the Gitea server, the owner of the repository, the name of the repository, and a personal access token.
|
|
#>
|
|
[cmdletbinding()]
|
|
param(
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$giteaURL = "https://gitea.norwichct.tech",
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$repoOwner,
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
[string]$repoName,
|
|
[int]$page,
|
|
[int]$limit,
|
|
[switch]$includeDrafts,
|
|
[switch]$includePreReleases,
|
|
[string]$token
|
|
)
|
|
|
|
begin {
|
|
# Use configuration if parameters aren't provided
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL') -or
|
|
-not $PSBoundParameters.ContainsKey('repoOwner') -or
|
|
-not $PSBoundParameters.ContainsKey('token')) {
|
|
|
|
$config = Get-GiteaConfiguration
|
|
if ($config) {
|
|
if (-not $PSBoundParameters.ContainsKey('giteaURL')) { $giteaURL = $config.giteaURL }
|
|
if (-not $PSBoundParameters.ContainsKey('repoOwner')) { $repoOwner = $config.defaultOwner }
|
|
if (-not $PSBoundParameters.ContainsKey('token')) { $token = $config.token }
|
|
}
|
|
}
|
|
|
|
# Validate that we have all required parameters
|
|
$missingParams = @()
|
|
if (-not $giteaURL) { $missingParams += "giteaURL" }
|
|
if (-not $repoOwner) { $missingParams += "repoOwner" }
|
|
if (-not $token) { $missingParams += "token" }
|
|
|
|
if ($missingParams.Count -gt 0) {
|
|
throw "Missing required parameters: $($missingParams -join ', '). Either provide them directly or set them with Set-GiteaConfiguration."
|
|
}
|
|
|
|
$headers = @{
|
|
"Authorization" = "token $token"
|
|
"Accept" = "application/json"
|
|
}
|
|
}
|
|
|
|
process {
|
|
Write-Verbose "Parameters:"
|
|
Write-Verbose "giteaURL: $giteaURL"
|
|
Write-Verbose "repoOwner: $repoOwner"
|
|
Write-Verbose "repoName: $repoName"
|
|
Write-Verbose "page: $page"
|
|
Write-Verbose "limit: $limit"
|
|
Write-Verbose "includeDrafts: $includeDrafts"
|
|
Write-Verbose "includePreReleases: $includePreReleases"
|
|
Write-Debug "Token: $token"
|
|
|
|
Write-Verbose "Processing releases for repository: $repoName"
|
|
$url = "$giteaURL"
|
|
$url += "/api/v1/repos"
|
|
$url += "/$repoOwner"
|
|
$url += "/$repoName"
|
|
$url += "/releases"
|
|
if ($includeDrafts -eq $true) {
|
|
$url += "?draft=true"
|
|
}
|
|
else {
|
|
$url += "?draft=false"
|
|
}
|
|
if ($includePreReleases -eq $true) {
|
|
$url += "&pre-release=true"
|
|
}
|
|
if ($page -gt 1) {
|
|
$url += "&page=$page"
|
|
}
|
|
if ($limit -gt 0) {
|
|
$url += "&limit=$limit"
|
|
}
|
|
Write-Verbose "URL: $url"
|
|
|
|
try {
|
|
$result = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
|
|
$releases = foreach ($release in $result) {
|
|
# Process each asset into its own object
|
|
$assets = foreach ($asset in $release.assets) {
|
|
[PSCustomObject]@{
|
|
Id = $asset.id
|
|
Name = $asset.name
|
|
Size = $asset.size
|
|
DownloadCount = $asset.download_count
|
|
CreatedAt = [DateTime]$asset.created_at
|
|
UUID = $asset.uuid
|
|
DownloadURL = $asset.browser_download_url
|
|
}
|
|
}
|
|
|
|
# Create the release object with nested author and assets
|
|
[PSCustomObject]@{
|
|
Id = $release.id
|
|
TagName = $release.tag_name
|
|
TargetCommit = $release.target_commitish
|
|
Name = $release.name
|
|
Description = $release.body
|
|
URL = $release.url
|
|
HTMLURL = $release.html_url
|
|
TarballURL = $release.tarball_url
|
|
ZipballURL = $release.zipball_url
|
|
UploadURL = $release.upload_url
|
|
IsDraft = $release.draft
|
|
IsPrerelease = $release.prerelease
|
|
CreatedAt = [DateTime]$release.created_at
|
|
PublishedAt = [DateTime]$release.published_at
|
|
Author = [PSCustomObject]@{
|
|
Id = $release.author.id
|
|
Username = $release.author.username
|
|
Email = $release.author.email
|
|
AvatarURL = $release.author.avatar_url
|
|
HTMLURL = $release.author.html_url
|
|
}
|
|
Assets = $assets
|
|
}
|
|
}
|
|
|
|
return $releases
|
|
}
|
|
catch {
|
|
Write-Error "Failed to retrieve file '$file' from Gitea: $_"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Export-ModuleMember -Function Set-GiteaConfiguration, Get-GiteaConfiguration, Get-GiteaFileContent, Invoke-GiteaFileDownload, Get-GiteaChildItem, Get-GiteaReleases |