fix(部署): 完整文件树一致时兼容 Squash 提交身份
This commit is contained in:
parent
55b1d9d7dc
commit
7322ba1bde
@ -28,6 +28,15 @@
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
release_includes_commit() {
|
||||
git merge-base --is-ancestor "$1" "$2" 2>/dev/null && return 0
|
||||
# Squash 改变提交身份;仅接受历史中完整文件树一致的提交。
|
||||
local previous_tree release_trees
|
||||
previous_tree="$(git rev-parse "${1}^{tree}" 2>/dev/null)" || return 1
|
||||
release_trees="$(git log --format=%T "$2" 2>/dev/null)" || return 1
|
||||
printf '%s\n' "$release_trees" | grep -Fx -- "$previous_tree" >/dev/null
|
||||
}
|
||||
|
||||
server='218.93.190.75'
|
||||
key_path=''
|
||||
password_file=''
|
||||
@ -522,7 +531,7 @@ if [ -n "$previous_sha" ]; then
|
||||
if [ "$previous_sha_exists" -eq 0 ]; then
|
||||
fail "线上当前版本 ${previous_sha} 在本地和 origin 都找不到(可能是未推送的分支),发布会把它覆盖。请让发布人先推送并合并进 main;确需撤回时显式添加 --allow-rollback。"
|
||||
fi
|
||||
if ! git merge-base --is-ancestor "$previous_sha" "$deploy_sha"; then
|
||||
if ! release_includes_commit "$previous_sha" "$deploy_sha"; then
|
||||
fail "线上当前版本($(git log -1 --format='%h %an %ad %s' --date=format:'%m-%d %H:%M' "$previous_sha"))不在本次发布的版本里,继续发布会把它及之后别人上线的改动撤掉。请先 git fetch 并把最新 main(或线上热修复分支)合入当前分支后重试;确需撤回时显式添加 --allow-rollback。"
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -50,6 +50,21 @@ $buildkitConfig = Join-Path $repoRoot 'deploy/buildkitd.toml'
|
||||
$regctlVersion = 'v0.11.5'
|
||||
$regctlSha256 = '9b83975d464e4ee5a9f899a3ead26072ed67e9a8ed0f582897bfad8141b45938'
|
||||
|
||||
function Test-CaiwuReleaseIncludesCommit {
|
||||
param([string]$PreviousSha, [string]$ReleaseSha)
|
||||
|
||||
git merge-base --is-ancestor $PreviousSha $ReleaseSha 2>$null
|
||||
if ($LASTEXITCODE -eq 0) { return $true }
|
||||
|
||||
# Squash 会改变提交身份;只接受发布历史中完整文件树一致的提交。
|
||||
# 不按提交说明、部分文件或 patch 相似度放行。
|
||||
$previousTree = (git rev-parse "${PreviousSha}^{tree}" 2>$null) -join ''
|
||||
if ($LASTEXITCODE -ne 0) { return $false }
|
||||
$releaseTrees = @(git log --format=%T $ReleaseSha 2>$null)
|
||||
if ($LASTEXITCODE -ne 0) { return $false }
|
||||
return $releaseTrees -contains $previousTree
|
||||
}
|
||||
|
||||
function Invoke-Native {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
@ -440,8 +455,7 @@ try {
|
||||
if (-not $previousShaExists) {
|
||||
throw "线上当前版本 $previousSha 在本地和 origin 都找不到(可能是未推送的分支),发布会把它覆盖。请让发布人先推送并合并进 main;确需撤回时显式添加 -AllowRollback。服务器未发生任何变更。"
|
||||
}
|
||||
git merge-base --is-ancestor $previousSha $deploySha 2>$null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
if (-not (Test-CaiwuReleaseIncludesCommit -PreviousSha $previousSha -ReleaseSha $deploySha)) {
|
||||
$previousInfo = (git log -1 --format='%h %an %ad %s' --date=format:'%m-%d %H:%M' $previousSha) -join ''
|
||||
throw "线上当前版本($previousInfo)不在本次发布的版本里,继续发布会把它及之后别人上线的改动撤掉。请先 git fetch 并把最新 main(或线上热修复分支)合入当前分支后重试;确需撤回时显式添加 -AllowRollback。服务器未发生任何变更。"
|
||||
}
|
||||
|
||||
71
scripts/test-release-inclusion.ps1
Normal file
71
scripts/test-release-inclusion.ps1
Normal file
@ -0,0 +1,71 @@
|
||||
param([string]$BashPath='bash')
|
||||
$ErrorActionPreference='Stop'
|
||||
$repoRoot=Split-Path $PSScriptRoot -Parent
|
||||
$psSource=[IO.File]::ReadAllText((Join-Path $repoRoot 'scripts/deploy-from-workstation.ps1'))
|
||||
$parseErrors=$null; $tokens=$null
|
||||
$ast=[Management.Automation.Language.Parser]::ParseInput($psSource,[ref]$tokens,[ref]$parseErrors)
|
||||
if($parseErrors.Count){throw 'Deployment PowerShell syntax invalid'}
|
||||
$function=$ast.Find({param($node) $node -is [Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq 'Test-CaiwuReleaseIncludesCommit'},$true)
|
||||
if(-not $function){throw 'PowerShell inclusion guard missing'}
|
||||
. ([ScriptBlock]::Create($function.Extent.Text))
|
||||
$bashSource=[IO.File]::ReadAllText((Join-Path $repoRoot 'scripts/deploy-from-mac.sh')).Replace("`r`n","`n")
|
||||
$bashFunction=[regex]::Match($bashSource,'(?ms)^release_includes_commit\(\) \{\n.*?^\}').Value
|
||||
if(-not $bashFunction){throw 'Bash inclusion guard missing'}
|
||||
|
||||
$fixture=Join-Path ([IO.Path]::GetTempPath()) ('caiwu-release-inclusion-'+[guid]::NewGuid().ToString('N'))
|
||||
New-Item -ItemType Directory -Path $fixture | Out-Null
|
||||
$expectedFixture=[IO.Path]::GetFullPath($fixture)
|
||||
function Invoke-FixtureGit {
|
||||
param([Parameter(ValueFromRemainingArguments=$true)][string[]]$GitArgs)
|
||||
$output=@(git @GitArgs 2>&1)
|
||||
if($LASTEXITCODE -ne 0){throw "Fixture Git failed: $($GitArgs[0])"}
|
||||
return $output
|
||||
}
|
||||
function New-FixtureCommit {
|
||||
param([string]$Value,[string]$Message)
|
||||
[IO.File]::WriteAllText((Join-Path $fixture 'value.txt'),$Value)
|
||||
Invoke-FixtureGit add value.txt | Out-Null
|
||||
Invoke-FixtureGit commit -m $Message | Out-Null
|
||||
return (Invoke-FixtureGit rev-parse HEAD) -join ''
|
||||
}
|
||||
Push-Location $fixture
|
||||
try {
|
||||
Invoke-FixtureGit init -b main | Out-Null
|
||||
Invoke-FixtureGit config user.name 'Release guard fixture' | Out-Null
|
||||
Invoke-FixtureGit config user.email 'release-guard@example.invalid' | Out-Null
|
||||
$base=New-FixtureCommit 'base' 'base'
|
||||
Invoke-FixtureGit switch -c hotfix | Out-Null
|
||||
$hotfix=New-FixtureCommit 'hotfix content' 'hotfix'
|
||||
Invoke-FixtureGit switch main | Out-Null
|
||||
$squashed=New-FixtureCommit 'hotfix content' 'squashed identity'
|
||||
$release=New-FixtureCommit 'next authorized change' 'new release'
|
||||
Invoke-FixtureGit switch -c unrelated $base | Out-Null
|
||||
$unrelated=New-FixtureCommit 'different hotfix content' 'hotfix'
|
||||
$cases=@(
|
||||
@{name='direct ancestor';previous=$base;release=$release;expected=$true},
|
||||
@{name='same commit';previous=$hotfix;release=$hotfix;expected=$true},
|
||||
@{name='identical squash tree at head';previous=$hotfix;release=$squashed;expected=$true},
|
||||
@{name='identical squash tree in release ancestry';previous=$hotfix;release=$release;expected=$true},
|
||||
@{name='real unmerged change';previous=$unrelated;release=$release;expected=$false},
|
||||
@{name='rollback to before hotfix';previous=$hotfix;release=$base;expected=$false},
|
||||
@{name='missing deployed commit';previous=('0'*40);release=$release;expected=$false},
|
||||
@{name='missing release commit';previous=$hotfix;release=('0'*40);expected=$false}
|
||||
)
|
||||
foreach($case in $cases){
|
||||
$actual=Test-CaiwuReleaseIncludesCommit -PreviousSha $case.previous -ReleaseSha $case.release
|
||||
if($actual -ne $case.expected){throw "PowerShell failed: $($case.name)"}
|
||||
$code=$bashFunction+"`nrelease_includes_commit "+'"$1" "$2"'
|
||||
& $BashPath --noprofile --norc -c $code guard $case.previous $case.release
|
||||
$bashActual=$LASTEXITCODE -eq 0
|
||||
if($bashActual -ne $case.expected){throw "Bash failed: $($case.name)"}
|
||||
}
|
||||
"PASS: $($cases.Count) real Git cases for both PowerShell and Bash guards"
|
||||
} finally {
|
||||
Pop-Location
|
||||
$resolved=(Resolve-Path -LiteralPath $fixture).Path
|
||||
if([IO.Path]::GetFullPath($resolved) -ne $expectedFixture -or (Get-Item -LiteralPath $resolved).Attributes.HasFlag([IO.FileAttributes]::ReparsePoint)){
|
||||
throw 'Refusing cleanup outside the exact generated fixture directory'
|
||||
}
|
||||
Remove-Item -LiteralPath $resolved -Recurse -Force
|
||||
}
|
||||
exit 0
|
||||
Loading…
x
Reference in New Issue
Block a user