fix: 识别线上热修 Squash 等价提交

部署防覆盖校验支持补丁等价判断,避免平台仅允许 Squash 时误拦已合入主线的线上热修复。
This commit is contained in:
qiuxiang 2026-11-06 14:51:54 +08:00
parent 2901bc6396
commit dd7d2d2e28
3 changed files with 48 additions and 5 deletions

View File

@ -30,11 +30,31 @@ set -euo pipefail
release_includes_commit() {
git merge-base --is-ancestor "$1" "$2" 2>/dev/null && return 0
# Squash 改变提交身份;仅接受历史中完整文件树一致的提交。
# 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
if printf '%s\n' "$release_trees" | grep -Fx -- "$previous_tree" >/dev/null; then
return 0
fi
# 线上热修复可能基于旧主线,Squash 后补丁相同但父提交不同。只有线上独有
# 的非合并提交都能在发布历史中找到等价补丁,且合并提交没有冲突解决内容时,
# 才允许通过;任何真实新增或丢失的改动仍会被拦截。
local unmerged_commits unique_merges merge conflict_resolution
unmerged_commits="$(git cherry "$2" "$1" 2>/dev/null)" || return 1
if printf '%s\n' "$unmerged_commits" | grep -q '^+ '; then
return 1
fi
unique_merges="$(git rev-list --merges "$1" "^$2" 2>/dev/null)" || return 1
while IFS= read -r merge; do
[ -n "$merge" ] || continue
conflict_resolution="$(git show --cc --format= "$merge" 2>/dev/null)" || return 1
[ -z "$conflict_resolution" ] || return 1
done <<EOF
$unique_merges
EOF
return 0
}
server='218.93.190.75'

View File

@ -62,13 +62,30 @@ function Test-CaiwuReleaseIncludesCommit {
git merge-base --is-ancestor $PreviousSha $ReleaseSha 2>$null
if ($LASTEXITCODE -eq 0) { return $true }
# Squash 会改变提交身份;只接受发布历史中完整文件树一致的提交。
# 不按提交说明、部分文件或 patch 相似度放行。
# Squash 会改变提交身份;先接受发布历史中完整文件树一致的提交。
$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
if ($releaseTrees -contains $previousTree) { return $true }
# 线上热修复可能基于旧主线,Squash 后补丁相同但父提交不同。只有线上独有
# 的非合并提交都能在发布历史中找到等价补丁,且合并提交没有冲突解决内容时,
# 才允许通过;任何真实新增或丢失的改动仍会被拦截。
$unmergedCommits = @(git cherry $ReleaseSha $PreviousSha 2>$null)
if ($LASTEXITCODE -ne 0) { return $false }
if ($unmergedCommits | Where-Object { $_ -match '^\+ ' }) { return $false }
$uniqueMerges = @(git rev-list --merges $PreviousSha "^$ReleaseSha" 2>$null)
if ($LASTEXITCODE -ne 0) { return $false }
foreach ($merge in $uniqueMerges) {
$conflictResolution = @(git show --cc --format= $merge 2>$null)
if ($LASTEXITCODE -ne 0 -or ($conflictResolution -join "`n").Trim() -ne '') {
return $false
}
}
return $true
}
function Invoke-Native {

View File

@ -39,6 +39,11 @@ try {
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 equivalent-patch $base | Out-Null
[IO.File]::WriteAllText((Join-Path $fixture 'context.txt'),'different parent tree')
Invoke-FixtureGit add context.txt | Out-Null
Invoke-FixtureGit commit -m 'different parent context' | Out-Null
$equivalentPatch=New-FixtureCommit 'hotfix content' 'equivalent squash on newer base'
Invoke-FixtureGit switch -c unrelated $base | Out-Null
$unrelated=New-FixtureCommit 'different hotfix content' 'hotfix'
$cases=@(
@ -46,6 +51,7 @@ try {
@{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='equivalent patch on different parent tree';previous=$hotfix;release=$equivalentPatch;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},