初始化: 完成Git Skill测试基线

Co-authored-by: 旅行呀~ <travelxiao@qq.com>
This commit is contained in:
2026-08-20 23:42:02 +08:00
commit 2cddf06b6c
30 changed files with 3422 additions and 0 deletions
+291
View File
@@ -0,0 +1,291 @@
# Shared helpers for git-init skill scripts.
# Dot-source only. Never prints token values.
$ErrorActionPreference = 'Stop'
if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) {
$PSNativeCommandUseErrorActionPreference = $false
}
# Windows PowerShell 5.1 promotes native stderr to a terminating error when
# $ErrorActionPreference='Stop', so 2>$null alone is not enough. This helper
# runs a native executable with stderr discarded and $LASTEXITCODE preserved.
function Invoke-GitSkillsNative {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[string[]]$ArgumentList = @()
)
$prevEap = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$output = @(& $FilePath @ArgumentList 2>$null | ForEach-Object { "$_" })
return ,$output
}
finally {
$ErrorActionPreference = $prevEap
}
}
function Invoke-GitSkillsNativeWithInput {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[string[]]$ArgumentList = @(),
[Parameter(Mandatory = $true)]
[string]$InputText
)
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $FilePath
$psi.UseShellExecute = $false
$psi.RedirectStandardInput = $true
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.CreateNoWindow = $true
$psi.Arguments = ($ArgumentList | ForEach-Object {
if ($_ -match '\s|"') { '"{0}"' -f ($_ -replace '"', '\"') } else { $_ }
}) -join ' '
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $psi
[void]$proc.Start()
foreach ($line in ($InputText -split "`r?`n", 0)) {
$proc.StandardInput.WriteLine($line)
}
$proc.StandardInput.Close()
$stdout = $proc.StandardOutput.ReadToEnd()
$stderr = $proc.StandardError.ReadToEnd()
$proc.WaitForExit()
$script:LastNativeExitCode = $proc.ExitCode
if ($stderr) { $null = $stderr }
if ([string]::IsNullOrWhiteSpace($stdout)) { return @() }
return @($stdout -split "`r?`n")
}
Add-Type -AssemblyName System.Security | Out-Null
$script:GitSkillsHome = Join-Path $env:USERPROFILE '.git-skills'
$script:ProfilePath = Join-Path $script:GitSkillsHome 'profile.json'
$script:TokenPath = Join-Path $script:GitSkillsHome 'token.dpapi'
$script:DefaultsPath = Join-Path $PSScriptRoot 'defaults.json'
function Get-GitSkillsDefaults {
if (-not (Test-Path -LiteralPath $script:DefaultsPath)) {
return [pscustomobject]@{
userName = 'Traveler'
userEmail = 'user@example.com'
}
}
return Get-Content -LiteralPath $script:DefaultsPath -Raw -Encoding UTF8 | ConvertFrom-Json
}
function Ensure-GitSkillsHome {
if (-not (Test-Path -LiteralPath $script:GitSkillsHome)) {
New-Item -ItemType Directory -Path $script:GitSkillsHome -Force | Out-Null
}
}
function Get-GitSkillsProfile {
if (-not (Test-Path -LiteralPath $script:ProfilePath)) {
return $null
}
return Get-Content -LiteralPath $script:ProfilePath -Raw -Encoding UTF8 | ConvertFrom-Json
}
function Save-GitSkillsProfile {
param(
[Parameter(Mandatory = $true)]
$Profile
)
Ensure-GitSkillsHome
$json = $Profile | ConvertTo-Json -Depth 5
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($script:ProfilePath, $json, $utf8Bom)
}
function Protect-SecretString {
param(
[Parameter(Mandatory = $true)]
[string]$PlainText
)
$bytes = [System.Text.Encoding]::UTF8.GetBytes($PlainText)
return [System.Security.Cryptography.ProtectedData]::Protect(
$bytes,
$null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser
)
}
function Unprotect-SecretBytes {
param(
[Parameter(Mandatory = $true)]
[byte[]]$Data
)
$bytes = [System.Security.Cryptography.ProtectedData]::Unprotect(
$Data,
$null,
[System.Security.Cryptography.DataProtectionScope]::CurrentUser
)
return [System.Text.Encoding]::UTF8.GetString($bytes)
}
function ConvertFrom-SecureStringPlain {
param(
[Parameter(Mandatory = $true)]
[System.Security.SecureString]$Secure
)
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secure)
try {
return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
}
finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
}
}
function Parse-GitRemoteUrl {
param(
[Parameter(Mandatory = $true)]
[string]$RemoteUrl
)
$url = $RemoteUrl.Trim()
if ([string]::IsNullOrWhiteSpace($url)) {
throw 'Remote URL is empty.'
}
function New-ParsedRemote {
param(
[string]$Protocol,
[string]$HostName,
[object]$Port,
[string]$Path
)
$portNum = $null
if ($null -ne $Port -and "$Port" -ne '') {
$portNum = [int]$Port
}
$credentialHost = $HostName
if ($Protocol -eq 'https' -or $Protocol -eq 'http') {
$defaultPort = if ($Protocol -eq 'https') { 443 } else { 80 }
if ($null -ne $portNum -and $portNum -ne $defaultPort) {
$credentialHost = '{0}:{1}' -f $HostName, $portNum
}
}
return [pscustomobject]@{
Protocol = $Protocol
Host = $HostName
Port = $portNum
Path = $Path
CredentialHost = $credentialHost
}
}
# Gitea / Git SCP: git@host:owner/repo.git
if ($url -match '^git@([^:]+):(.+)$') {
return New-ParsedRemote -Protocol 'ssh' -HostName $Matches[1] -Port $null -Path $Matches[2]
}
# ssh://git@host:2222/owner/repo.git or ssh://git@host/owner/repo.git
if ($url -match '^ssh://(?:[^@/]+@)?(\[[^\]]+\]|[^:/]+)(?::(\d+))?/(.+)$') {
$port = if ($Matches[2]) { [int]$Matches[2] } else { $null }
return New-ParsedRemote -Protocol 'ssh' -HostName $Matches[1].Trim('[]') -Port $port -Path $Matches[3]
}
try {
$uri = [Uri]$url
}
catch {
throw "Unsupported remote URL: $url"
}
if (-not $uri.IsAbsoluteUri -or [string]::IsNullOrWhiteSpace($uri.Host)) {
throw "Unsupported remote URL: $url"
}
$port = $null
if (-not $uri.IsDefaultPort) {
$port = $uri.Port
}
return New-ParsedRemote -Protocol $uri.Scheme -HostName $uri.Host -Port $port -Path $uri.AbsolutePath.TrimStart('/')
}
function Write-StatusLine {
param(
[Parameter(Mandatory = $true)]
[string]$Key,
[Parameter(Mandatory = $true)]
[AllowEmptyString()]
[string]$Value
)
Write-Output ("{0}={1}" -f $Key, $Value)
}
function Get-CoAuthorTrailerLines {
param(
$GitProfile = $null
)
if ($null -eq $GitProfile) {
$GitProfile = Get-GitSkillsProfile
}
if ($null -eq $GitProfile) {
return @()
}
$lines = New-Object System.Collections.Generic.List[string]
$primaryName = ([string]$GitProfile.userName).Trim()
$primaryEmail = ([string]$GitProfile.userEmail).Trim()
if (-not [string]::IsNullOrWhiteSpace($primaryName) -and -not [string]::IsNullOrWhiteSpace($primaryEmail)) {
[void]$lines.Add(('Co-authored-by: {0} <{1}>' -f $primaryName, $primaryEmail))
}
$extras = @()
if ($GitProfile.PSObject.Properties.Name -contains 'coAuthors' -and $null -ne $GitProfile.coAuthors) {
$extras = @($GitProfile.coAuthors)
}
foreach ($item in $extras) {
$name = ''
$email = ''
if ($null -eq $item) { continue }
if ($item -is [string]) {
if ($item -match '^\s*(.+?)\s*<([^>]+)>\s*$') {
$name = $Matches[1].Trim()
$email = $Matches[2].Trim()
}
}
else {
$name = ([string]$item.name).Trim()
$email = ([string]$item.email).Trim()
}
if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($email)) {
continue
}
$line = 'Co-authored-by: {0} <{1}>' -f $name, $email
if (-not $lines.Contains($line)) {
[void]$lines.Add($line)
}
}
return @($lines)
}
function Format-CommitMessageWithTrailers {
param(
[Parameter(Mandatory = $true)]
[string]$Subject,
$GitProfile = $null
)
$subject = $Subject.Trim()
if ([string]::IsNullOrWhiteSpace($subject)) {
throw 'Commit subject is empty.'
}
$trailers = Get-CoAuthorTrailerLines -GitProfile $GitProfile
if ($trailers.Count -eq 0) {
return $subject
}
return ($subject + "`n`n" + ($trailers -join "`n"))
}