|
|
|
|
@@ -1,3 +1,79 @@
|
|
|
|
|
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 $env:USERPROFILE -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 $env:USERPROFILE -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 {
|
|
|
|
|
|
|
|
|
|
<#
|
|
|
|
|
@@ -42,28 +118,58 @@ Function Get-GiteaFileContent {
|
|
|
|
|
|
|
|
|
|
[CmdletBinding()]
|
|
|
|
|
param(
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$giteaURL = "https://gitea.norwichct.tech",
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$repoOwner,
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$repoName,
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$branch = "main",
|
|
|
|
|
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
|
|
|
|
|
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
|
|
|
|
|
[Alias('FullName', 'Path')]
|
|
|
|
|
[string[]]$filePath,
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[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-Verbose "token: $token"
|
|
|
|
|
Write-Debug "Token: $token"
|
|
|
|
|
Write-Verbose "decode: $decode"
|
|
|
|
|
|
|
|
|
|
$headers = @{
|
|
|
|
|
@@ -123,6 +229,82 @@ Function Get-GiteaFileContent {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
@@ -131,6 +313,9 @@ Function Invoke-GiteaFileDownload {
|
|
|
|
|
.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.
|
|
|
|
|
|
|
|
|
|
@@ -143,6 +328,15 @@ Function Invoke-GiteaFileDownload {
|
|
|
|
|
.PARAMETER force
|
|
|
|
|
A switch parameter to force overwriting of an existing file at the output path.
|
|
|
|
|
|
|
|
|
|
.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.
|
|
|
|
|
|
|
|
|
|
.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.
|
|
|
|
|
|
|
|
|
|
.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"
|
|
|
|
|
@@ -150,46 +344,532 @@ Function Invoke-GiteaFileDownload {
|
|
|
|
|
.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\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(Mandatory)]
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$downloadURL,
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[string]$token,
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$outputPath,
|
|
|
|
|
[switch]$force
|
|
|
|
|
[switch]$force,
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$filePath,
|
|
|
|
|
[switch]$PreserveRelativePath,
|
|
|
|
|
[Parameter(ValueFromPipelineByPropertyName)]
|
|
|
|
|
[string]$type
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# If the output path is not specified, use the current directory and the file name from the download URL (everything after the last '/' and before the last character after the last '.', inclusive of the extension)
|
|
|
|
|
if (-not $outputPath) {
|
|
|
|
|
# Get the file name from the download URL
|
|
|
|
|
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"
|
|
|
|
|
|
|
|
|
|
# Create a WebClient to be reused
|
|
|
|
|
$webClient = New-Object System.Net.WebClient
|
|
|
|
|
$webClient.Headers.Add("Authorization", "token $token")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
process {
|
|
|
|
|
Write-Verbose "File path: $filePath"
|
|
|
|
|
Write-Verbose "Type: $type"
|
|
|
|
|
|
|
|
|
|
# Skip directories and provide a verbose message
|
|
|
|
|
if ($type -eq "dir") {
|
|
|
|
|
Write-Verbose "Skipping directory: $filePath"
|
|
|
|
|
return [PSCustomObject]@{
|
|
|
|
|
Path = $filePath
|
|
|
|
|
SourceURL = $downloadURL
|
|
|
|
|
FilePath = $filePath
|
|
|
|
|
Type = $type
|
|
|
|
|
Success = $true
|
|
|
|
|
Error = "Skipped - item is a directory"
|
|
|
|
|
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: $filePath"
|
|
|
|
|
Write-Error $errorMsg
|
|
|
|
|
return [PSCustomObject]@{
|
|
|
|
|
Path = $filePath
|
|
|
|
|
SourceURL = $downloadURL
|
|
|
|
|
FilePath = $filePath
|
|
|
|
|
Type = $type
|
|
|
|
|
Success = $false
|
|
|
|
|
Error = $errorMsg
|
|
|
|
|
Timestamp = Get-Date
|
|
|
|
|
FileSize = $null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Verbose "Processing download URL: $downloadURL"
|
|
|
|
|
|
|
|
|
|
# Set the output path based on parameters
|
|
|
|
|
$currentOutputPath = ""
|
|
|
|
|
|
|
|
|
|
# Get the file name from the download URL or file path
|
|
|
|
|
$outputFileName = $downloadURL.Substring($downloadURL.LastIndexOf("/") + 1)
|
|
|
|
|
# Clean the file name by removing any query string parameters and HTML encoded characters
|
|
|
|
|
$outputFileName = [System.Uri]::UnescapeDataString($outputFileName.Split("?")[0])
|
|
|
|
|
|
|
|
|
|
if ($PreserveRelativePath -and $filePath) {
|
|
|
|
|
# If PreserveRelativePath is used, set up the directory structure
|
|
|
|
|
Write-Verbose "Preserving relative path structure for: $filePath"
|
|
|
|
|
|
|
|
|
|
if ($outputPath) {
|
|
|
|
|
# If outputPath is specified, use it as the base directory
|
|
|
|
|
$baseDir = $outputPath
|
|
|
|
|
Write-Verbose "Using base directory: $baseDir"
|
|
|
|
|
|
|
|
|
|
# Combine the base directory with the file path
|
|
|
|
|
$currentOutputPath = Join-Path -Path $baseDir -ChildPath $filePath
|
|
|
|
|
} else {
|
|
|
|
|
# If no outputPath is specified, use the current directory as base
|
|
|
|
|
$currentOutputPath = Join-Path -Path (Get-Location) -ChildPath $filePath
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Normalize path separators
|
|
|
|
|
$currentOutputPath = $currentOutputPath.Replace("/", [System.IO.Path]::DirectorySeparatorChar)
|
|
|
|
|
Write-Verbose "Output path with preserved structure: $currentOutputPath"
|
|
|
|
|
} else {
|
|
|
|
|
# Standard path handling (unchanged from original)
|
|
|
|
|
if ($outputPath) {
|
|
|
|
|
$currentOutputPath = $outputPath
|
|
|
|
|
} else {
|
|
|
|
|
# Append the outputFileName to the current location
|
|
|
|
|
$outputPath = Join-Path -Path (Get-Location) -ChildPath $outputFileName
|
|
|
|
|
$currentOutputPath = Join-Path -Path (Get-Location) -ChildPath $outputFileName
|
|
|
|
|
}
|
|
|
|
|
Write-Verbose "Output path: $currentOutputPath"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if((Test-Path -Path $outputPath -PathType Leaf) -and (-not $force)) {
|
|
|
|
|
Write-Error "The file '$outputPath' already exists. Use the -Force switch to overwrite the file."
|
|
|
|
|
return $false | Out-Null
|
|
|
|
|
$result = [PSCustomObject]@{
|
|
|
|
|
Path = $currentOutputPath
|
|
|
|
|
SourceURL = $downloadURL
|
|
|
|
|
FilePath = $filePath
|
|
|
|
|
Type = $type
|
|
|
|
|
Success = $false
|
|
|
|
|
Error = $null
|
|
|
|
|
Timestamp = Get-Date
|
|
|
|
|
FileSize = $null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$headers = @{
|
|
|
|
|
"Authorization" = "token $token"
|
|
|
|
|
if((Test-Path -Path $currentOutputPath -PathType Leaf) -and (-not $force)) {
|
|
|
|
|
$errorMsg = "The file '$currentOutputPath' already exists. Use the -Force switch to overwrite the file."
|
|
|
|
|
Write-Error $errorMsg
|
|
|
|
|
$result.Error = $errorMsg
|
|
|
|
|
return $result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Invoke-RestMethod -Uri $downloadURL -Method Get -Headers $headers -OutFile $outputPath
|
|
|
|
|
return $true | Out-Null
|
|
|
|
|
# Create the directory structure if it doesn't exist
|
|
|
|
|
$directory = Split-Path -Path $currentOutputPath -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 $currentOutputPath"
|
|
|
|
|
|
|
|
|
|
# Use synchronous download
|
|
|
|
|
$webClient.DownloadFile($downloadURL, $currentOutputPath)
|
|
|
|
|
|
|
|
|
|
Write-Verbose "File downloaded successfully to $currentOutputPath"
|
|
|
|
|
|
|
|
|
|
# Get file info for the result object
|
|
|
|
|
if (Test-Path -Path $currentOutputPath -PathType Leaf) {
|
|
|
|
|
$fileInfo = Get-Item -Path $currentOutputPath
|
|
|
|
|
$result.FileSize = $fileInfo.Length
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result.Success = $true
|
|
|
|
|
return $result
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
Write-Error "Failed to download file from Gitea: $_"
|
|
|
|
|
return $false | Out-Null
|
|
|
|
|
$errorMsg = "Failed to download file from Gitea: $_"
|
|
|
|
|
Write-Error $errorMsg
|
|
|
|
|
$result.Error = $errorMsg
|
|
|
|
|
return $result
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Export-ModuleMember -Function Get-GiteaFileContent, Invoke-GiteaFileDownload
|
|
|
|
|
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 the path format - replace backslashes with forward slashes and trim trailing slashes
|
|
|
|
|
$normalizedPath = $path -replace '\\', '/' -replace '/$', ''
|
|
|
|
|
$encodedPath = [System.Uri]::EscapeDataString($normalizedPath)
|
|
|
|
|
Write-Verbose "Normalized path: $normalizedPath"
|
|
|
|
|
Write-Verbose "Encoded path: $encodedPath"
|
|
|
|
|
$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)"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$itemObj = [PSCustomObject]@{
|
|
|
|
|
name = $item.name
|
|
|
|
|
Path = $item.path
|
|
|
|
|
type = $item.type
|
|
|
|
|
size = $item.size
|
|
|
|
|
sha = $item.sha
|
|
|
|
|
downloadURL = $item.download_url
|
|
|
|
|
Success = $true
|
|
|
|
|
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
|
|
|
|
|
Success = $true
|
|
|
|
|
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
|
|
|
|
|
Success = $false
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Export-ModuleMember -Function Set-GiteaConfiguration, Get-GiteaConfiguration, Get-GiteaFileContent, Invoke-GiteaFileDownload, Get-GiteaChildItem, Get-GiteaLFSFile
|