diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ae5f380..4b9b155d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [Unreleased](https://github.com/ScoopInstaller/Scoop/compare/master...develop) + +### Features +- **scoop-update:** Add support for parallel syncing buckets in PowerShell 7 and improve output ([#5122](https://github.com/ScoopInstaller/Scoop/pull/5122)) + +### Code Refactoring +- **git:** Use Invoke-Git() with direct path to git.exe to prevent spawning shim subprocesses ([#5122](https://github.com/ScoopInstaller/Scoop/pull/5122)) + ## [v0.3.1](https://github.com/ScoopInstaller/Scoop/compare/v0.3.0...v0.3.1) - 2022-11-15 ### Features diff --git a/bin/scoop.ps1 b/bin/scoop.ps1 index 6d96c68b..fd2fd41f 100644 --- a/bin/scoop.ps1 +++ b/bin/scoop.ps1 @@ -20,8 +20,8 @@ switch ($subCommand) { } ({ $subCommand -in @('-v', '--version') }) { Write-Host 'Current Scoop version:' - if ((Test-CommandAvailable git) -and (Test-Path "$PSScriptRoot\..\.git") -and (get_config SCOOP_BRANCH 'master') -ne 'master') { - git -C "$PSScriptRoot\.." --no-pager log --oneline HEAD -n 1 + if (Test-GitAvailable -and (Test-Path "$PSScriptRoot\..\.git") -and (get_config SCOOP_BRANCH 'master') -ne 'master') { + Invoke-Git -Path "$PSScriptRoot\.." -ArgumentList @('log', 'HEAD', '-1', '--oneline') } else { $version = Select-String -Pattern '^## \[(v[\d.]+)\].*?([\d-]+)$' -Path "$PSScriptRoot\..\CHANGELOG.md" Write-Host $version.Matches.Groups[1].Value -ForegroundColor Cyan -NoNewline @@ -31,9 +31,9 @@ switch ($subCommand) { Get-LocalBucket | ForEach-Object { $bucketLoc = Find-BucketDirectory $_ -Root - if ((Test-Path "$bucketLoc\.git") -and (Test-CommandAvailable git)) { + if (Test-GitAvailable -and (Test-Path "$bucketLoc\.git")) { Write-Host "'$_' bucket:" - git -C "$bucketLoc" --no-pager log --oneline HEAD -n 1 + Invoke-Git -Path $bucketLoc -ArgumentList @('log', 'HEAD', '-1', '--oneline') Write-Host '' } } diff --git a/lib/buckets.ps1 b/lib/buckets.ps1 index 2a035612..704d51a8 100644 --- a/lib/buckets.ps1 +++ b/lib/buckets.ps1 @@ -99,8 +99,8 @@ function list_buckets { $bucket = [Ordered]@{ Name = $_ } $path = Find-BucketDirectory $_ -Root if ((Test-Path (Join-Path $path '.git')) -and (Get-Command git -ErrorAction SilentlyContinue)) { - $bucket.Source = git -C $path config remote.origin.url - $bucket.Updated = git -C $path log --format='%aD' -n 1 | Get-Date + $bucket.Source = Invoke-Git -Path $path -ArgumentList @('config', 'remote.origin.url') + $bucket.Updated = Invoke-Git -Path $path -ArgumentList @('log', "--format='%aD'", '-n', '1') } else { $bucket.Source = friendly_path $path $bucket.Updated = (Get-Item "$path\bucket").LastWriteTime @@ -113,7 +113,7 @@ function list_buckets { } function add_bucket($name, $repo) { - if (!(Test-CommandAvailable git)) { + if (!(Test-GitAvailable)) { error "Git is required for buckets. Run 'scoop install git' and try again." return 1 } @@ -130,7 +130,7 @@ function add_bucket($name, $repo) { } foreach ($bucket in Get-LocalBucket) { if (Test-Path -Path "$bucketsdir\$bucket\.git") { - $remote = git -C "$bucketsdir\$bucket" config --get remote.origin.url + $remote = Invoke-Git -Path "$bucketsdir\$bucket" -ArgumentList @('config', '--get', 'remote.origin.url') if ((Convert-RepositoryUri -Uri $remote) -eq $uni_repo) { warn "Bucket $bucket already exists for $repo" return 2 @@ -139,14 +139,14 @@ function add_bucket($name, $repo) { } Write-Host 'Checking repo... ' -NoNewline - $out = git_cmd ls-remote $repo 2>&1 + $out = Invoke-Git -ArgumentList @('ls-remote', $repo) 2>&1 if ($LASTEXITCODE -ne 0) { error "'$repo' doesn't look like a valid git repository`n`nError given:`n$out" return 1 } ensure $bucketsdir | Out-Null $dir = ensure $dir - git_cmd clone "$repo" "`"$dir`"" -q + Invoke-Git -ArgumentList @('clone', $repo, $dir, '-q') Write-Host 'OK' success "The $name bucket was added successfully." return 0 @@ -169,7 +169,7 @@ function new_issue_msg($app, $bucket, $title, $body) { $bucket_path = "$bucketsdir\$bucket" if (Test-Path $bucket_path) { - $remote = git -C "$bucket_path" config --get remote.origin.url + $remote = Invoke-Git -Path $bucket_path -ArgumentList @('config', '--get', 'remote.origin.url') # Support ssh and http syntax # git@PROVIDER:USER/REPO.git # https://PROVIDER/USER/REPO.git diff --git a/lib/core.ps1 b/lib/core.ps1 index bc0365eb..3d10baf2 100644 --- a/lib/core.ps1 +++ b/lib/core.ps1 @@ -128,13 +128,78 @@ function setup_proxy() { } } -function git_cmd { +function Invoke-Git { + [CmdletBinding()] + [OutputType([String])] + param( + [Parameter(Mandatory = $false, Position = 0)] + [Alias('PSPath', 'Path')] + [ValidateNotNullOrEmpty()] + [String] + $WorkingDirectory, + [Parameter(Mandatory = $true, Position = 1)] + [Alias('Args')] + [String[]] + $ArgumentList + ) + $proxy = get_config PROXY - $cmd = "git $($args | ForEach-Object { "$_ " })" - if ($proxy -and $proxy -ne 'none') { - $cmd = "SET HTTPS_PROXY=$proxy&&SET HTTP_PROXY=$proxy&&$cmd" + $git = Get-HelperPath -Helper Git + $arguments = $ArgumentList -join ' ' + $cmd = "`"$git`" $arguments" + + if ($WorkingDirectory) { + $cmd = "`"$git`" -C `"$WorkingDirectory`" $arguments" + } + $sb = [scriptblock]::Create("& $cmd") + + if([String]::IsNullOrEmpty($proxy) -or $proxy -eq 'none') { + return Invoke-Command $sb + } + + if($arguments -Match '\b(clone|checkout|pull|fetch|ls-remote)\b') { + $old_https = $env:HTTPS_PROXY + $old_http = $env:HTTP_PROXY + try { + # convert proxy setting for git + if ($proxy.StartsWith('currentuser@')) { + $proxy = $proxy.Replace('currentuser@', ':@') + } + $env:HTTPS_PROXY = $proxy + $env:HTTP_PROXY = $proxy + return Invoke-Command $sb + } + catch { + error $_ + return + } + finally { + $env:HTTPS_PROXY = $old_https + $env:HTTP_PROXY = $old_http + } + } + + return Invoke-Command $sb +} + +function Invoke-GitLog { + [CmdletBinding()] + Param ( + [Parameter(Mandatory, ValueFromPipeline)] + [String]$Path, + [Parameter(Mandatory, ValueFromPipeline)] + [String]$CommitHash, + [String]$Name = '' + ) + Process { + if ($Name) { + if ($Name.Length -gt 12) { + $Name = "$($Name.Substring(0, 10)).." + } + $Name = "%Cgreen$($Name.PadRight(12, ' ').Substring(0, 12))%Creset " + } + Invoke-Git -Path $Path -ArgumentList @('--no-pager', 'log', '--color', '--no-decorate', "--grep='^(chore)'", '--invert-grep', '--abbrev=12', "--format='tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s $Name%C(cyan)%cr%Creset'", "$CommitHash..HEAD") } - cmd.exe /d /c $cmd } # helper functions @@ -293,12 +358,16 @@ Function Test-CommandAvailable { Return [Boolean](Get-Command $Name -ErrorAction Ignore) } +Function Test-GitAvailable { + Return [Boolean](Test-Path (Get-HelperPath -Helper Git) -ErrorAction Ignore) +} + function Get-HelperPath { [CmdletBinding()] [OutputType([String])] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] - [ValidateSet('7zip', 'Lessmsi', 'Innounp', 'Dark', 'Aria2', 'Zstd')] + [ValidateSet('Git', '7zip', 'Lessmsi', 'Innounp', 'Dark', 'Aria2', 'Zstd')] [String] $Helper ) @@ -307,6 +376,14 @@ function Get-HelperPath { } process { switch ($Helper) { + 'Git' { + $internalgit = "$(versiondir 'git' 'current')\mingw64\bin\git.exe" + if (Test-Path $internalgit) { + $HelperPath = $internalgit + } else { + $HelperPath = (Get-Command git -ErrorAction Ignore).Source + } + } '7zip' { $HelperPath = Get-AppFilePath '7zip' '7z.exe' if ([String]::IsNullOrEmpty($HelperPath)) { diff --git a/libexec/scoop-info.ps1 b/libexec/scoop-info.ps1 index f4346a60..74e2530c 100644 --- a/libexec/scoop-info.ps1 +++ b/libexec/scoop-info.ps1 @@ -84,7 +84,7 @@ if ($manifest.depends) { if (Test-Path $manifest_file) { if (Get-Command git -ErrorAction Ignore) { - $gitinfo = (git -C (Split-Path $manifest_file) log -1 -s --format='%aD#%an' $manifest_file 2> $null) -Split '#' + $gitinfo = (Invoke-Git -Path (Split-Path $manifest_file) -ArgumentList @('log', '-1', '-s', "--format='%aD#%an'", $manifest_file) 2> $null) -Split '#' } if ($gitinfo) { $item.'Updated at' = $gitinfo[0] | Get-Date diff --git a/libexec/scoop-status.ps1 b/libexec/scoop-status.ps1 index 28596ce8..c72f75bc 100644 --- a/libexec/scoop-status.ps1 +++ b/libexec/scoop-status.ps1 @@ -21,10 +21,10 @@ if (!(Get-FormatData ScoopStatus)) { function Test-UpdateStatus($repopath) { if (Test-Path "$repopath\.git") { - git_cmd -C "`"$repopath`"" fetch -q origin + Invoke-Git -Path $repopath -ArgumentList @('fetch', '-q', 'origin') $script:network_failure = 128 -eq $LASTEXITCODE - $branch = git -C $repopath branch --show-current - $commits = git -C $repopath log "HEAD..origin/$branch" --oneline + $branch = Invoke-Git -Path $repopath -ArgumentList @('branch', '--show-current') + $commits = Invoke-Git -Path $repopath -ArgumentList @('log', "HEAD..origin/$branch", '--oneline') if ($commits) { return $true } else { return $false } } else { diff --git a/libexec/scoop-update.ps1 b/libexec/scoop-update.ps1 index ad670f86..3bcdcb75 100644 --- a/libexec/scoop-update.ps1 +++ b/libexec/scoop-update.ps1 @@ -56,14 +56,18 @@ if(($PSVersionTable.PSVersion.Major) -lt 5) { } $show_update_log = get_config SHOW_UPDATE_LOG $true -function update_scoop($show_update_log) { +function Sync-Scoop { + [CmdletBinding()] + Param ( + [Switch]$Log + ) # Test if Scoop Core is hold if(Test-ScoopCoreOnHold) { return } # check for git - if (!(Test-CommandAvailable git)) { abort "Scoop uses Git to update itself. Run 'scoop install git' and try again." } + if (!(Test-GitAvailable)) { abort "Scoop uses Git to update itself. Run 'scoop install git' and try again." } Write-Host "Updating Scoop..." $currentdir = fullpath $(versiondir 'scoop' 'current') @@ -72,7 +76,7 @@ function update_scoop($show_update_log) { $olddir = "$currentdir\..\old" # get git scoop - git_cmd clone -q $configRepo --branch $configBranch --single-branch "`"$newdir`"" + Invoke-Git -ArgumentList @('clone', '-q', $configRepo, '--branch', $configBranch, '--single-branch', $newdir) # check if scoop was successful downloaded if (!(Test-Path "$newdir\bin\scoop.ps1")) { @@ -93,18 +97,18 @@ function update_scoop($show_update_log) { Remove-Item "$currentdir\..\old" -Recurse -Force -ErrorAction SilentlyContinue } - $previousCommit = git -C "$currentdir" rev-parse HEAD - $currentRepo = git -C "$currentdir" config remote.origin.url - $currentBranch = git -C "$currentdir" branch + $previousCommit = Invoke-Git -Path $currentdir -ArgumentList @('rev-parse', 'HEAD') + $currentRepo = Invoke-Git -Path $currentdir -ArgumentList @('config', 'remote.origin.url') + $currentBranch = Invoke-Git -Path $currentdir -ArgumentList @('branch') $isRepoChanged = !($currentRepo -match $configRepo) $isBranchChanged = !($currentBranch -match "\*\s+$configBranch") # Stash uncommitted changes - if (git -C "$currentdir" diff HEAD --name-only) { + if (Invoke-Git -Path $currentdir -ArgumentList @('diff', 'HEAD', '--name-only')) { if (get_config AUTOSTASH_ON_CONFLICT) { warn "Uncommitted changes detected. Stashing..." - git -C "$currentdir" stash push -m "WIP at $([System.DateTime]::Now.ToString('o'))" -u -q + Invoke-Git -Path $currentdir -ArgumentList @('stash', 'push', '-m', "WIP at $([System.DateTime]::Now.ToString('o'))", '-u', '-q') } else { warn "Uncommitted changes detected. Update aborted." return @@ -113,26 +117,26 @@ function update_scoop($show_update_log) { # Change remote url if the repo is changed if ($isRepoChanged) { - git -C "$currentdir" config remote.origin.url "$configRepo" + Invoke-Git -Path $currentdir -ArgumentList @('config', 'remote.origin.url', $configRepo) } # Fetch and reset local repo if the repo or the branch is changed if ($isRepoChanged -or $isBranchChanged) { # Reset git fetch refs, so that it can fetch all branches (GH-3368) - git -C "$currentdir" config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' + Invoke-Git -Path $currentdir -ArgumentList @('config', 'remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*') # fetch remote branch - git_cmd -C "`"$currentdir`"" fetch --force origin "refs/heads/`"$configBranch`":refs/remotes/origin/$configBranch" -q + Invoke-Git -Path $currentdir -ArgumentList @('fetch', '--force', 'origin', "refs/heads/$configBranch`:refs/remotes/origin/$configBranch", '-q') # checkout and track the branch - git_cmd -C "`"$currentdir`"" checkout -B $configBranch -t origin/$configBranch -q + Invoke-Git -Path $currentdir -ArgumentList @('checkout', '-B', $configBranch, '-t', "origin/$configBranch", '-q') # reset branch HEAD - git -C "$currentdir" reset --hard origin/$configBranch -q + Invoke-Git -Path $currentdir -ArgumentList @('reset', '--hard', "origin/$configBranch", '-q') } else { - git_cmd -C "`"$currentdir`"" pull -q + Invoke-Git -Path $currentdir -ArgumentList @('pull', '-q') } $res = $lastexitcode - if ($show_update_log) { - git -C "$currentdir" --no-pager log --no-decorate --grep='^(chore)' --invert-grep --format='tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s %C(cyan)%cr%Creset' "$previousCommit..HEAD" + if ($Log) { + Invoke-GitLog -Path $currentdir -CommitHash $previousCommit } if ($res -ne 0) { @@ -140,47 +144,63 @@ function update_scoop($show_update_log) { } } - # This should have been deprecated after 2019-05-12 - # if ((Get-LocalBucket) -notcontains 'main') { - # info "The main bucket of Scoop has been separated to 'https://github.com/ScoopInstaller/Main'" - # info "Adding main bucket..." - # add_bucket 'main' - # } - shim "$currentdir\bin\scoop.ps1" $false } -function update_bucket($show_update_log) { - # check for git - if (!(Test-CommandAvailable git)) { abort "Scoop uses Git to update main bucket and others. Run 'scoop install git' and try again." } +function Sync-Bucket { + Param ( + [Switch]$Log + ) + Write-Host "Updating Buckets..." - foreach ($bucket in Get-LocalBucket) { - Write-Host "Updating '$bucket' bucket..." - - $bucketLoc = Find-BucketDirectory $bucket -Root - - if (!(Test-Path (Join-Path $bucketLoc '.git'))) { - if ($bucket -eq 'main') { - # Make sure main bucket, which was downloaded as zip, will be properly "converted" into git - Write-Host " Converting 'main' bucket to git repo..." - $status = rm_bucket 'main' - if ($status -ne 0) { - abort "Failed to remove local 'main' bucket." - } - $status = add_bucket 'main' (known_bucket_repo 'main') - if ($status -ne 0) { - abort "Failed to add remote 'main' bucket." - } - } else { - Write-Host "'$bucket' is not a git repository. Skipped." - } - continue + if (!(Test-Path (Join-Path (Find-BucketDirectory 'main' -Root) '.git'))) { + info "Converting 'main' bucket to git repo..." + $status = rm_bucket 'main' + if ($status -ne 0) { + abort "Failed to remove local 'main' bucket." } + $status = add_bucket 'main' (known_bucket_repo 'main') + if ($status -ne 0) { + abort "Failed to add remote 'main' bucket." + } + } - $previousCommit = git -C "$bucketLoc" rev-parse HEAD - git_cmd -C "`"$bucketLoc`"" pull -q - if ($show_update_log) { - git -C "$bucketLoc" --no-pager log --no-decorate --grep='^(chore)' --invert-grep --format='tformat: * %C(yellow)%h%Creset %<|(72,trunc)%s %C(cyan)%cr%Creset' "$previousCommit..HEAD" + + $buckets = Get-LocalBucket | ForEach-Object { + $path = Find-BucketDirectory $_ -Root + return @{ + name = $_ + valid = Test-Path (Join-Path $path '.git') + path = $path + } + } + + $buckets | Where-Object { !$_.valid } | ForEach-Object { Write-Host "'$($_.name)' is not a git repository. Skipped." } + + if ($PSVersionTable.PSVersion.Major -ge 7) { + # Parallel parameter is available since PowerShell 7 + $buckets | Where-Object { $_.valid } | ForEach-Object -ThrottleLimit 5 -Parallel { + . "$using:PSScriptRoot\..\lib\core.ps1" + + $bucketLoc = $_.path + $name = $_.name + + $previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD') + Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q') + if ($using:Log) { + Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit + } + } + } else { + $buckets | Where-Object { $_.valid } | ForEach-Object { + $bucketLoc = $_.path + $name = $_.name + + $previousCommit = Invoke-Git -Path $bucketLoc -ArgumentList @('rev-parse', 'HEAD') + Invoke-Git -Path $bucketLoc -ArgumentList @('pull', '-q') + if ($Log) { + Invoke-GitLog -Path $bucketLoc -Name $name -CommitHash $previousCommit + } } } } @@ -321,8 +341,8 @@ if (-not ($apps -or $all)) { error 'scoop update: --no-cache is invalid when is not specified.' exit 1 } - update_scoop $show_update_log - update_bucket $show_update_log + Sync-Scoop -Log:$show_update_log + Sync-Bucket -Log:$show_update_log set_config LAST_UPDATE ([System.DateTime]::Now.ToString('o')) | Out-Null success 'Scoop was updated successfully!' } else { @@ -336,8 +356,8 @@ if (-not ($apps -or $all)) { $apps_param = $apps if ($updateScoop) { - update_scoop $show_update_log - update_bucket $show_update_log + Sync-Scoop -Log:$show_update_log + Sync-Bucket -Log:$show_update_log set_config LAST_UPDATE ([System.DateTime]::Now.ToString('o')) | Out-Null success 'Scoop was updated successfully!' }