mirror of
https://github.com/ScoopInstaller/Scoop.git
synced 2025-10-30 06:07:56 +00:00
Compare commits
6 Commits
9f62e55862
...
1fae80b2c2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fae80b2c2 | ||
|
|
cf345e272e | ||
|
|
99fb8383b7 | ||
|
|
0a52f0d0de | ||
|
|
becd429581 | ||
|
|
d3f681ce03 |
329
lib/manifest.ps1
329
lib/manifest.ps1
@@ -202,7 +202,7 @@ function Get-RelativePathCompat($from, $to) {
|
||||
return $relativePath -replace '/', '\'
|
||||
}
|
||||
|
||||
function Get-HistoricalManifestFromDB($app, $bucket, $requestedVersion) {
|
||||
function Find-HistoricalManifestInCache($app, $bucket, $requestedVersion) {
|
||||
if (!(get_config USE_SQLITE_CACHE)) {
|
||||
return $null
|
||||
}
|
||||
@@ -217,26 +217,25 @@ function Get-HistoricalManifestFromDB($app, $bucket, $requestedVersion) {
|
||||
. "$PSScriptRoot\database.ps1"
|
||||
}
|
||||
|
||||
# First try exact match
|
||||
$dbResult = Get-ScoopDBItem -Name $app -Bucket $bucket -Version $requestedVersion
|
||||
if ($dbResult.Rows.Count -gt 0) {
|
||||
$row = $dbResult.Rows[0]
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$tempManifestPath = "$(usermanifestsdir)\$app.json"
|
||||
$row.manifest | Out-UTF8File -FilePath $tempManifestPath
|
||||
|
||||
# Parse the manifest to get its actual version
|
||||
$manifest = $row.manifest | ConvertFrom-Json
|
||||
$manifestVersion = if ($manifest.version) { $manifest.version } else { $requestedVersion }
|
||||
return @{ path = $tempManifestPath; version = $manifestVersion; source = "sqlite_exact_match" }
|
||||
}
|
||||
# Strictly follow DB contract: must be DataTable with at least one row
|
||||
if (-not ($dbResult -is [System.Data.DataTable])) { return $null }
|
||||
if ($dbResult.Rows.Count -eq 0) { return $null }
|
||||
|
||||
# No exact match found, return null (no compatibility matching)
|
||||
$manifestText = $dbResult.Rows[0]['manifest']
|
||||
if ([string]::IsNullOrWhiteSpace($manifestText)) { return $null }
|
||||
|
||||
$manifestObj = $null
|
||||
try { $manifestObj = $manifestText | ConvertFrom-Json -ErrorAction Stop } catch {}
|
||||
$manifestVersion = if ($manifestObj -and $manifestObj.version) { $manifestObj.version } else { $requestedVersion }
|
||||
|
||||
return @{ ManifestText = $manifestText; version = $manifestVersion; source = "sqlite_exact_match" }
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-HistoricalManifestFromGitHistory($app, $bucket, $requestedVersion) {
|
||||
function Find-HistoricalManifestInGit($app, $bucket, $requestedVersion) {
|
||||
# Only proceed if git history is enabled
|
||||
if (!(get_config USE_GIT_HISTORY $true)) {
|
||||
return $null
|
||||
@@ -263,153 +262,81 @@ function Get-HistoricalManifestFromGitHistory($app, $bucket, $requestedVersion)
|
||||
$relativeManifestPath = $relativeManifestPath -replace '\\', '/'
|
||||
|
||||
try {
|
||||
$gitLogOutput = Invoke-Git -Path $bucketDir -ArgumentList @('log', '--follow', '--format=format:%H%n%s', '--', $relativeManifestPath) # Removed 2>$null to see git errors
|
||||
# Prefer precise regex match on version line, fallback to -S literal
|
||||
$pattern = '"version"\s*:\s*"' + [regex]::Escape($requestedVersion) + '"'
|
||||
$commits = @()
|
||||
$outG = Invoke-Git -Path $bucketDir -ArgumentList @('log','--follow','-n','1','--format=%H','-G',$pattern,'--',$relativeManifestPath)
|
||||
if ($outG) { $commits = @($outG | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) }
|
||||
|
||||
if (!$gitLogOutput) {
|
||||
warn "No git history found for '$app' in bucket '$bucket' (file: $relativeManifestPath)."
|
||||
return $null
|
||||
if ($commits.Count -eq 0) {
|
||||
$searchLiteral = '"version": "' + $requestedVersion + '"'
|
||||
$outS = Invoke-Git -Path $bucketDir -ArgumentList @('log','--follow','-n','1','--format=%H','-S',$searchLiteral,'--',$relativeManifestPath)
|
||||
if ($outS) { $commits = @($outS | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) }
|
||||
}
|
||||
|
||||
$foundVersions = [System.Collections.Generic.List[hashtable]]::new()
|
||||
$processedHashes = [System.Collections.Generic.HashSet[string]]::new()
|
||||
if ($commits.Count -eq 0) { return $null }
|
||||
|
||||
$versionsFoundForPreviousMinor = @{}
|
||||
$limitPreviousMinorCount = 5
|
||||
$requestedVersionParts = $requestedVersion -split '\.'
|
||||
$previousMinorVersionString = ''
|
||||
if ($requestedVersionParts.Count -ge 2 -and $requestedVersionParts[1] -match '^\d+$') {
|
||||
$currentMinor = [int]$requestedVersionParts[1]
|
||||
if ($currentMinor -gt 0) {
|
||||
$previousMinorVersionString = "$($requestedVersionParts[0]).$($currentMinor - 1)"
|
||||
$h = $commits[0]
|
||||
|
||||
# First try parent snapshot (latest state before change), then the change itself
|
||||
foreach ($spec in @("$h^","$h")) {
|
||||
$content = Invoke-Git -Path $bucketDir -ArgumentList @('show', "$spec`:$relativeManifestPath")
|
||||
if (-not $content -or ($LASTEXITCODE -ne 0)) { continue }
|
||||
if ($content -is [Array]) { $content = $content -join "`n" }
|
||||
try {
|
||||
$obj = $content | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch { continue }
|
||||
if ($obj -and $obj.version -eq $requestedVersion) {
|
||||
return @{ ManifestText = $content; version = $requestedVersion; source = "git_manifest:$spec" }
|
||||
}
|
||||
}
|
||||
|
||||
$maxCommitsToProcess = 200 # Hard cap to prevent excessive runtimes
|
||||
# Fallback: iterate recent commits that touched the version string and validate
|
||||
$outAll = Invoke-Git -Path $bucketDir -ArgumentList @('log','--follow','--format=%H','-G',$pattern,'--',$relativeManifestPath)
|
||||
$allCommits = @()
|
||||
if ($outAll) { $allCommits = @($outAll | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) }
|
||||
|
||||
for ($i = 0; $i -lt $gitLogOutput.Count; $i += 2) {
|
||||
if ($processedHashes.Count -ge $maxCommitsToProcess) {
|
||||
info "Processed $maxCommitsToProcess commits for $app. Stopping search to prevent excessive runtime."
|
||||
break
|
||||
}
|
||||
|
||||
$hash = $gitLogOutput[$i]
|
||||
$subject = if (($i + 1) -lt $gitLogOutput.Count) { $gitLogOutput[$i + 1] } else { '' }
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($hash) -or !$processedHashes.Add($hash)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$versionFromMessage = $null
|
||||
if ($subject -match "(?:'$app': Update to version |Update to |Release |Tag |v)($([char]34)?([0-9]+\.[0-9]+(?:\.[0-9]+){0,2}(?:[a-zA-Z0-9._+-]*))\1?)") {
|
||||
$versionFromMessage = $Matches[2]
|
||||
}
|
||||
|
||||
if ($versionFromMessage -eq $requestedVersion) {
|
||||
info "Potential exact match '$versionFromMessage' found in commit message for $app (hash $hash). Verifying manifest..."
|
||||
$manifestContent = Invoke-Git -Path $bucketDir -ArgumentList @('show', "$hash`:$relativeManifestPath")
|
||||
if ($manifestContent -and ($LASTEXITCODE -eq 0)) {
|
||||
if ($manifestContent -is [Array]) {
|
||||
$manifestContent = $manifestContent -join "`n"
|
||||
}
|
||||
$manifestObj = $null; try { $manifestObj = $manifestContent | ConvertFrom-Json -ErrorAction Stop } catch {}
|
||||
if ($manifestObj -and $manifestObj.version -eq $requestedVersion) {
|
||||
info "Exact version '$requestedVersion' for '$app' confirmed from manifest in commit $hash."
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$tempManifestPath = "$(usermanifestsdir)\$app.json"
|
||||
$manifestContent | Out-UTF8File -FilePath $tempManifestPath
|
||||
return @{ path = $tempManifestPath; version = $requestedVersion; source = "git_commit_message:$hash" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$manifestContent = Invoke-Git -Path $bucketDir -ArgumentList @('show', "$hash`:$relativeManifestPath")
|
||||
if ($manifestContent -and ($LASTEXITCODE -eq 0)) {
|
||||
if ($manifestContent -is [Array]) {
|
||||
$manifestContent = $manifestContent -join "`n"
|
||||
}
|
||||
$manifestObj = $null;
|
||||
try {
|
||||
$manifestObj = $manifestContent | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch {
|
||||
warn "Failed to parse manifest content from commit $hash for app $app. Skipping this commit."
|
||||
# Consider logging $manifestContent if debugging is needed
|
||||
continue # Skip to the next commit
|
||||
}
|
||||
|
||||
if ($manifestObj -and $manifestObj.version) {
|
||||
if ($manifestObj.version -eq $requestedVersion) {
|
||||
info "Exact version '$requestedVersion' for '$app' found in manifest (commit $hash)."
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$tempManifestPath = "$(usermanifestsdir)\$app.json"
|
||||
if ($PSVersionTable.PSVersion.Major -ge 6) {
|
||||
$utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($false)
|
||||
Set-Content -Path $tempManifestPath -Value $manifestContent -Encoding $utf8NoBomEncoding -NoNewline:$false
|
||||
} else {
|
||||
# PowerShell 5 compatibility
|
||||
$manifestContent | Out-UTF8File -FilePath $tempManifestPath
|
||||
}
|
||||
return @{ path = $tempManifestPath; version = $requestedVersion; source = "git_manifest:$hash" }
|
||||
}
|
||||
$foundVersions.Add(@{
|
||||
version = $manifestObj.version
|
||||
hash = $hash
|
||||
manifest = $manifestContent
|
||||
})
|
||||
|
||||
if ($previousMinorVersionString -ne '' -and $manifestObj.version.StartsWith($previousMinorVersionString)) {
|
||||
if (!$versionsFoundForPreviousMinor.ContainsKey($previousMinorVersionString)) {
|
||||
$versionsFoundForPreviousMinor[$previousMinorVersionString] = 0
|
||||
}
|
||||
$versionsFoundForPreviousMinor[$previousMinorVersionString]++
|
||||
if ($versionsFoundForPreviousMinor[$previousMinorVersionString] -ge $limitPreviousMinorCount) {
|
||||
info "Reached limit of $limitPreviousMinorCount versions for previous minor '$previousMinorVersionString' while checking $app. Stopping search."
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($foundVersions.Count % 20 -eq 0 -and $foundVersions.Count -gt 0) {
|
||||
info "Found $($foundVersions.Count) historical versions for $app so far..."
|
||||
}
|
||||
}
|
||||
} elseif ($LASTEXITCODE -ne 0) {
|
||||
warn "git show $hash`:$relativeManifestPath failed for $app."
|
||||
foreach ($c in $allCommits) {
|
||||
$content = Invoke-Git -Path $bucketDir -ArgumentList @('show', "$c`:$relativeManifestPath")
|
||||
if (-not $content -or ($LASTEXITCODE -ne 0)) { continue }
|
||||
if ($content -is [Array]) { $content = $content -join "`n" }
|
||||
try { $obj = $content | ConvertFrom-Json -ErrorAction Stop } catch { continue }
|
||||
if ($obj -and $obj.version -eq $requestedVersion) {
|
||||
return @{ ManifestText = $content; version = $requestedVersion; source = "git_manifest:$c" }
|
||||
}
|
||||
}
|
||||
|
||||
if ($foundVersions.Count -eq 0) {
|
||||
warn "No valid historical versions found for '$app' in bucket '$bucket'."
|
||||
return $null
|
||||
}
|
||||
|
||||
# No exact match found - display all available versions to help user choose
|
||||
$allAvailableVersions = ($foundVersions | Sort-Object {
|
||||
try { [version]($_.version -replace '[^\d\.].*$', '') } catch { $_.version }
|
||||
} -Descending).version
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "No exact match found for version '$requestedVersion' for app '$app'."
|
||||
Write-Host "Available versions in git history (newest to oldest):"
|
||||
Write-Host ""
|
||||
|
||||
# Group versions for better display
|
||||
$displayCount = [Math]::Min(50, $allAvailableVersions.Count) # Show up to 50 versions
|
||||
for ($i = 0; $i -lt $displayCount; $i++) {
|
||||
Write-Host " $($allAvailableVersions[$i])"
|
||||
}
|
||||
|
||||
if ($allAvailableVersions.Count -gt $displayCount) {
|
||||
Write-Host " ... and $($allAvailableVersions.Count - $displayCount) more versions"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "To install a specific version, use: scoop install $app@<version>"
|
||||
Write-Host ""
|
||||
|
||||
return $null
|
||||
} catch { return $null }
|
||||
}
|
||||
|
||||
} catch {
|
||||
warn "Error searching git history for '$app': $($_.Exception.Message)"
|
||||
return $null
|
||||
function Find-HistoricalManifest($app, $bucket, $version) {
|
||||
# Orchestrates historical manifest lookup using available providers (DB → Git)
|
||||
$result = $null
|
||||
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
$result = Find-HistoricalManifestInCache $app $bucket $version
|
||||
if ($result) {
|
||||
if ($result.ManifestText) {
|
||||
$path = Write-ManifestToUserCache -App $app -ManifestText $result.ManifestText
|
||||
return @{ path = $path; version = $result.version; source = $result.source }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
if (get_config USE_GIT_HISTORY $true) {
|
||||
$result = Find-HistoricalManifestInGit $app $bucket $version
|
||||
if ($result) {
|
||||
if ($result.ManifestText) {
|
||||
$path = Write-ManifestToUserCache -App $app -ManifestText $result.ManifestText
|
||||
return @{ path = $path; version = $result.version; source = $result.source }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
|
||||
@@ -420,106 +347,25 @@ function generate_user_manifest($app, $bucket, $version) {
|
||||
return manifest_path $app $bucket
|
||||
}
|
||||
|
||||
warn "Given version ($version) does not match manifest ($($manifest.version))"
|
||||
# Try historical providers via orchestrator
|
||||
$historicalResult = Find-HistoricalManifest $app $bucket $version
|
||||
if ($historicalResult) { return $historicalResult.path }
|
||||
|
||||
$historicalResult = $null
|
||||
|
||||
# Try SQLite cache first if enabled
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
info "Searching for version '$version' in cache..."
|
||||
$historicalResult = Get-HistoricalManifestFromDB $app $bucket $version
|
||||
if ($historicalResult) {
|
||||
info "Found version '$($historicalResult.version)' for '$app' in cache."
|
||||
return $historicalResult.path
|
||||
}
|
||||
# No historical manifest; try autoupdate if available
|
||||
if (!($manifest.autoupdate)) {
|
||||
abort "Could not find manifest for '$app@$version' and no autoupdate is available"
|
||||
}
|
||||
|
||||
# Try git history if cache didn't find it
|
||||
if (!$historicalResult) {
|
||||
info "Searching for version '$version' in git history..."
|
||||
$historicalResult = Get-HistoricalManifestFromGitHistory $app $bucket $version
|
||||
if ($historicalResult) {
|
||||
return $historicalResult.path
|
||||
}
|
||||
}
|
||||
|
||||
# If no historical version found, provide helpful guidance
|
||||
if (!$historicalResult) {
|
||||
# Try to provide additional context about what versions are available
|
||||
$currentVersion = $manifest.version
|
||||
if ($currentVersion) {
|
||||
info "Current version available: $currentVersion"
|
||||
info "To install the current version, use: scoop install $app"
|
||||
}
|
||||
|
||||
# Check if we have autoupdate capability for fallback
|
||||
if ($manifest.autoupdate) {
|
||||
info "This app supports autoupdate - attempting to generate manifest for version $version"
|
||||
} else {
|
||||
warn "'$app' does not have autoupdate capability."
|
||||
Write-Host "Available options:"
|
||||
Write-Host " 1. Install current version: scoop install $app"
|
||||
Write-Host " 2. Check if the requested version exists in other buckets"
|
||||
Write-Host " 3. Contact the bucket maintainer to add historical version support"
|
||||
Write-Host ""
|
||||
abort "Could not find manifest for '$app@$version' and no autoupdate available"
|
||||
}
|
||||
}
|
||||
|
||||
# Fallback to autoupdate generation
|
||||
warn "No historical version found. Attempting to generate manifest for '$app' ($version)"
|
||||
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$manifest_path = "$(usermanifestsdir)\$app.json"
|
||||
|
||||
# Check SQLite cache for exact cached manifest (this is different from historical search)
|
||||
if (get_config USE_SQLITE_CACHE) {
|
||||
$cached_manifest = (Get-ScoopDBItem -Name $app -Bucket $bucket -Version $version).manifest
|
||||
if ($cached_manifest) {
|
||||
$cached_manifest | Out-UTF8File $manifest_path
|
||||
return $manifest_path
|
||||
}
|
||||
}
|
||||
|
||||
if (!($manifest.autoupdate)) {
|
||||
abort "'$app' does not have autoupdate capability and no historical version found`r`ncouldn't find manifest for '$app@$version'"
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-AutoUpdate $app $manifest_path $manifest $version $(@{ })
|
||||
return $manifest_path
|
||||
} catch {
|
||||
Write-Host -ForegroundColor DarkRed "Could not install $app@$version"
|
||||
Write-Host -ForegroundColor Yellow "Autoupdate failed for version $version"
|
||||
|
||||
# Provide helpful guidance when autoupdate fails
|
||||
Write-Host "Possible reasons:"
|
||||
Write-Host " - Version $version may not exist or be available for download"
|
||||
Write-Host " - Download URLs may have changed or be inaccessible"
|
||||
Write-Host " - The version format may be incompatible with autoupdate patterns"
|
||||
Write-Host ""
|
||||
Write-Host "Suggestions:"
|
||||
Write-Host " 1. Install current version: scoop install $app"
|
||||
Write-Host " 2. Try a different version that was shown in the available list"
|
||||
Write-Host " 3. Check the app's official releases or download page"
|
||||
Write-Host ""
|
||||
|
||||
# If autoupdate fails and we haven't tried git history yet, try it as final fallback
|
||||
if (!$historicalResult -and !(get_config USE_SQLITE_CACHE)) {
|
||||
warn 'Autoupdate failed. Trying git history as final fallback...'
|
||||
$fallbackResult = Get-HistoricalManifestFromGitHistory $app $bucket $version
|
||||
if ($fallbackResult) {
|
||||
warn "Using historical manifest as fallback for '$app' version '$($fallbackResult.version)'"
|
||||
return $fallbackResult.path
|
||||
}
|
||||
}
|
||||
|
||||
# Final failure - provide comprehensive guidance
|
||||
Write-Host -ForegroundColor Red "All attempts to find or generate manifest for '$app@$version' failed."
|
||||
warn "Autoupdate failed for '$app@$version'"
|
||||
abort "Installation of '$app@$version' is not possible"
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function url($manifest, $arch) { arch_specific 'url' $manifest $arch }
|
||||
@@ -528,3 +374,16 @@ function uninstaller($manifest, $arch) { arch_specific 'uninstaller' $manifest $
|
||||
function hash($manifest, $arch) { arch_specific 'hash' $manifest $arch }
|
||||
function extract_dir($manifest, $arch) { arch_specific 'extract_dir' $manifest $arch }
|
||||
function extract_to($manifest, $arch) { arch_specific 'extract_to' $manifest $arch }
|
||||
|
||||
# Helper: write manifest text to user manifests cache directory and return path
|
||||
function Write-ManifestToUserCache {
|
||||
param(
|
||||
[Parameter(Mandatory=$true, Position=0)][string]$App,
|
||||
[Parameter(Mandatory=$true, Position=1)][string]$ManifestText
|
||||
)
|
||||
ensure (usermanifestsdir) | Out-Null
|
||||
$tempManifestPath = "$(usermanifestsdir)\$App.json"
|
||||
$ManifestText | Out-UTF8File -FilePath $tempManifestPath
|
||||
return $tempManifestPath
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
BeforeAll {
|
||||
. "$PSScriptRoot\..\lib\json.ps1"
|
||||
. "$PSScriptRoot\..\lib\core.ps1"
|
||||
. "$PSScriptRoot\..\lib\manifest.ps1"
|
||||
. "$PSScriptRoot\..\lib\buckets.ps1"
|
||||
. "$PSScriptRoot\..\lib\database.ps1"
|
||||
. "$PSScriptRoot\..\lib\autoupdate.ps1"
|
||||
}
|
||||
|
||||
Describe 'JSON parse and beautify' -Tag 'Scoop' {
|
||||
Context 'Parse JSON' {
|
||||
It 'success with valid json' {
|
||||
{ parse_json "$PSScriptRoot\fixtures\manifest\wget.json" } | Should -Not -Throw
|
||||
$parsed = parse_json "$PSScriptRoot\fixtures\manifest\wget.json"
|
||||
$parsed | Should -Not -Be $null
|
||||
}
|
||||
It 'fails with invalid json' {
|
||||
{ parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json" } | Should -Throw
|
||||
It 'returns null and warns with invalid json' {
|
||||
Mock warn {}
|
||||
{ parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json" } | Should -Not -Throw
|
||||
$parsed = parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json"
|
||||
$parsed | Should -Be $null
|
||||
Should -Invoke -CommandName warn -Times 1
|
||||
}
|
||||
}
|
||||
Context 'Beautify JSON' {
|
||||
@@ -84,3 +94,144 @@ Describe 'Manifest Validator' -Tag 'Validator' {
|
||||
$validator.Errors | Select-Object -Last 1 | Should -Match 'Required properties are missing from object: version\.'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Get-RelativePathCompat' -Tag 'Scoop' {
|
||||
It 'returns relative path for child path' {
|
||||
$from = 'C:\root\bucket'
|
||||
$to = 'C:\root\bucket\foo\bar.json'
|
||||
Get-RelativePathCompat $from $to | Should -Be 'foo\bar.json'
|
||||
}
|
||||
It 'returns original when different drive/scheme' {
|
||||
$from = 'C:\root\bucket'
|
||||
$to = 'D:\other\file.json'
|
||||
Get-RelativePathCompat $from $to | Should -Be $to
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Find-HistoricalManifestInCache' -Tag 'Scoop' {
|
||||
It 'returns $null when sqlite cache disabled' {
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
$result = Find-HistoricalManifestInCache 'foo' 'main' '1.0.0'
|
||||
$result | Should -Be $null
|
||||
}
|
||||
|
||||
It 'returns manifest text and version when cache has exact match' {
|
||||
$tempUM = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock get_config -ParameterFilter { $name -in @('use_sqlite_cache','use_git_history') } { $true }
|
||||
Mock Get-ScoopDBItem {
|
||||
$dt = New-Object System.Data.DataTable
|
||||
[void]$dt.Columns.Add('manifest')
|
||||
$row = $dt.NewRow()
|
||||
$row['manifest'] = '{"version":"1.2.3"}'
|
||||
[void]$dt.Rows.Add($row)
|
||||
Write-Output $dt -NoEnumerate
|
||||
}
|
||||
Mock ensure {}
|
||||
$result = Find-HistoricalManifestInCache 'foo' 'main' '1.2.3'
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$result.version | Should -Be '1.2.3'
|
||||
$result.ManifestText | Should -Match '"version":"1.2.3"'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Find-HistoricalManifestInGit' -Tag 'Scoop' {
|
||||
It 'returns $null when git history search disabled' {
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_git_history' } { $false }
|
||||
$result = Find-HistoricalManifestInGit 'foo' 'main' '1.0.0'
|
||||
$result | Should -Be $null
|
||||
}
|
||||
|
||||
It 'returns manifest text on version match' {
|
||||
$bucketRoot = 'C:\b\root'
|
||||
$innerBucket = 'C:\b\root\bucket'
|
||||
$umdir = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_git_history' } { $true }
|
||||
Mock Find-BucketDirectory -ParameterFilter { $Root } { $bucketRoot }
|
||||
Mock Find-BucketDirectory -ParameterFilter { -not $Root } { $innerBucket }
|
||||
Mock Test-Path -ParameterFilter { $Path -eq (Join-Path $bucketRoot '.git') } { $true }
|
||||
Mock Test-Path -ParameterFilter { $Path -eq $innerBucket -and $PathType -eq 'Container' } { $true }
|
||||
# Behavior-oriented mocks: using HEAD should yield a wrong version
|
||||
Mock Invoke-Git -ParameterFilter { $ArgumentList[0] -eq 'show' -and $ArgumentList[1] -like 'HEAD*' } { $global:LASTEXITCODE = 0; return '{"version":"2.0.0"}' }
|
||||
Mock Invoke-Git -ParameterFilter { $ArgumentList[0] -eq 'show' } { $global:LASTEXITCODE = 0; return '{"version":"1.0.0"}' }
|
||||
Mock Invoke-Git -ParameterFilter { $ArgumentList[0] -eq 'log' } { @('abcdef0123456789') }
|
||||
|
||||
$result = Find-HistoricalManifestInGit 'foo' 'main' '1.0.0'
|
||||
$result | Should -Not -BeNullOrEmpty
|
||||
$result.version | Should -Be '1.0.0'
|
||||
$result.ManifestText | Should -Match '"version":"1.0.0"'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'generate_user_manifest (history-aware)' -Tag 'Scoop' {
|
||||
It 'returns manifest_path when versions match' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='1.0.0' }, 'main', $null }
|
||||
Mock manifest_path { 'C:\path\foo.json' }
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be 'C:\path\foo.json'
|
||||
}
|
||||
|
||||
It 'prefers history orchestrator hit (cache) when enabled' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0' }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -in @('use_sqlite_cache','use_git_history') } { $true }
|
||||
Mock Find-HistoricalManifest { @{ path = 'C:\cache\foo.json'; version = '1.0.0'; source='sqlite_exact_match' } }
|
||||
|
||||
Mock info {}
|
||||
Mock warn {}
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be 'C:\cache\foo.json'
|
||||
Should -Invoke -CommandName Find-HistoricalManifest -Times 1
|
||||
|
||||
}
|
||||
|
||||
It 'falls back to git history when cache misses' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0' }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -in @('use_sqlite_cache','use_git_history') } { $true }
|
||||
Mock Find-HistoricalManifest { @{ path = 'C:\git\foo.json'; version = '1.0.0'; source='git_manifest:hash' } }
|
||||
Mock info {}
|
||||
Mock warn {}
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be 'C:\git\foo.json'
|
||||
Should -Invoke -CommandName Find-HistoricalManifest -Times 1
|
||||
|
||||
}
|
||||
|
||||
It 'uses autoupdate when no history found and autoupdate exists' {
|
||||
$umdir = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0'; autoupdate=@{} }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
Mock Find-HistoricalManifest { $null }
|
||||
|
||||
Mock ensure {}
|
||||
Mock usermanifestsdir { $umdir }
|
||||
Mock Invoke-AutoUpdate {}
|
||||
$p = generate_user_manifest 'foo' 'main' '1.0.0'
|
||||
$p | Should -Be (Join-Path $umdir 'foo.json')
|
||||
}
|
||||
|
||||
It 'on autoupdate failure aborts with concise message' {
|
||||
$umdir = Join-Path $env:TEMP 'ScoopTestsUM'
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0'; autoupdate=@{} }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
Mock Find-HistoricalManifest { $null }
|
||||
Mock ensure {}
|
||||
Mock usermanifestsdir { $umdir }
|
||||
Mock Invoke-AutoUpdate { throw 'fail' }
|
||||
Mock warn {}
|
||||
Mock info {}
|
||||
Mock Write-Host {}
|
||||
Mock abort { throw 'aborted' }
|
||||
{ generate_user_manifest 'foo' 'main' '1.0.0' } | Should -Throw
|
||||
}
|
||||
|
||||
It 'aborts when no history and no autoupdate' {
|
||||
Mock Get-Manifest -ParameterFilter { $app -eq 'main/foo' } { 'foo', [pscustomobject]@{ version='2.0.0' }, 'main', $null }
|
||||
Mock get_config -ParameterFilter { $name -eq 'use_sqlite_cache' } { $false }
|
||||
Mock Find-HistoricalManifest { $null }
|
||||
|
||||
Mock warn {}
|
||||
Mock info {}
|
||||
Mock abort { throw 'aborted' }
|
||||
{ generate_user_manifest 'foo' 'main' '1.0.0' } | Should -Throw
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user