mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
* fix(scripts): resolve pr tooling from a configurable root and preflight host git * fix(scripts): preserve PR tooling contracts in dependency checks and fixtures * test(scripts): match cross-checkout PR identity diagnostics * fix(scripts): preserve GitHub CLI host selection for PR reads * test(scripts): inject remote base drift at hosted verification
249 lines
7 KiB
Bash
249 lines
7 KiB
Bash
# shellcheck source=scripts/pr-lib/host-tools.sh
|
|
source "${BASH_SOURCE[0]%/*}/host-tools.sh" || return 1
|
|
|
|
changelog_helper_root() {
|
|
cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd
|
|
}
|
|
|
|
changelog_attribution_script() {
|
|
printf '%s\n' "$(changelog_helper_root)/scripts/check-changelog-attributions.mjs"
|
|
}
|
|
|
|
|
|
validate_changelog_attribution_policy() {
|
|
node "$(changelog_attribution_script)" CHANGELOG.md
|
|
}
|
|
|
|
changelog_thanks_required_for_contributor() {
|
|
local contrib="${1:-}"
|
|
[ -n "$contrib" ] || return 1
|
|
node "$(changelog_attribution_script)" --is-forbidden-handle "$contrib" && return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
changelog_explicit_human_thanks_required_for_contributor() {
|
|
local contrib="${1:-}"
|
|
[ -n "$contrib" ] || return 1
|
|
node "$(changelog_attribution_script)" --requires-explicit-human-thanks "$contrib"
|
|
}
|
|
|
|
validate_changelog_entry_for_pr() {
|
|
local pr="$1"
|
|
local contrib="$2"
|
|
|
|
local added_lines
|
|
added_lines=$(pr_git diff --unified=0 "$PR_MAIN_SHA...HEAD" -- CHANGELOG.md | awk '
|
|
/^\+\+\+/ { next }
|
|
/^\+/ { print substr($0, 2) }
|
|
')
|
|
|
|
if [ -z "$added_lines" ]; then
|
|
echo "CHANGELOG.md is in diff but no added lines were detected."
|
|
exit 1
|
|
fi
|
|
|
|
local pr_pattern
|
|
pr_pattern="(#$pr|openclaw#$pr)"
|
|
|
|
local with_pr
|
|
with_pr=$(printf '%s\n' "$added_lines" | grep -Ein "$pr_pattern" || true)
|
|
if [ -z "$with_pr" ]; then
|
|
echo "CHANGELOG.md update must reference PR #$pr (for example, (#$pr))."
|
|
exit 1
|
|
fi
|
|
|
|
local diff_file
|
|
diff_file=$(mktemp)
|
|
pr_git diff --unified=0 "$PR_MAIN_SHA...HEAD" -- CHANGELOG.md > "$diff_file"
|
|
|
|
if ! awk -v pr_pattern="$pr_pattern" '
|
|
BEGIN {
|
|
line_no = 0
|
|
file_line_count = 0
|
|
issue_count = 0
|
|
}
|
|
FNR == NR {
|
|
if ($0 ~ /^@@ /) {
|
|
if (match($0, /\+[0-9]+/)) {
|
|
line_no = substr($0, RSTART + 1, RLENGTH - 1) + 0
|
|
} else {
|
|
line_no = 0
|
|
}
|
|
next
|
|
}
|
|
if ($0 ~ /^\+\+\+/) {
|
|
next
|
|
}
|
|
if ($0 ~ /^\+/) {
|
|
if (line_no > 0) {
|
|
added[line_no] = 1
|
|
added_text = substr($0, 2)
|
|
if (added_text ~ pr_pattern) {
|
|
pr_added_lines[++pr_added_count] = line_no
|
|
pr_added_text[line_no] = added_text
|
|
}
|
|
line_no++
|
|
}
|
|
next
|
|
}
|
|
if ($0 ~ /^-/) {
|
|
next
|
|
}
|
|
if (line_no > 0) {
|
|
line_no++
|
|
}
|
|
next
|
|
}
|
|
{
|
|
changelog[FNR] = $0
|
|
file_line_count = FNR
|
|
}
|
|
END {
|
|
active_release_line = 0
|
|
bare_release_line = 0
|
|
active_release_name = "unreleased"
|
|
for (i = 1; i <= file_line_count; i++) {
|
|
if (changelog[i] !~ /^## /) {
|
|
continue
|
|
}
|
|
heading = tolower(changelog[i])
|
|
if (heading ~ /^##[[:space:]]+.+\([[:space:]]*unreleased[[:space:]]*\)[[:space:]]*$/) {
|
|
active_release_line = i
|
|
active_release_name = changelog[i]
|
|
break
|
|
}
|
|
if (heading == "## unreleased" && bare_release_line == 0) {
|
|
bare_release_line = i
|
|
}
|
|
}
|
|
if (active_release_line == 0 && bare_release_line != 0) {
|
|
active_release_line = bare_release_line
|
|
active_release_name = changelog[bare_release_line]
|
|
}
|
|
|
|
for (idx = 1; idx <= pr_added_count; idx++) {
|
|
entry_line = pr_added_lines[idx]
|
|
release_line = 0
|
|
section_line = 0
|
|
for (i = entry_line; i >= 1; i--) {
|
|
if (section_line == 0 && changelog[i] ~ /^### /) {
|
|
section_line = i
|
|
continue
|
|
}
|
|
if (changelog[i] ~ /^## /) {
|
|
release_line = i
|
|
break
|
|
}
|
|
}
|
|
if (release_line == 0 || release_line != active_release_line) {
|
|
printf "CHANGELOG.md PR-linked entry must be in %s: line %d: %s\n", active_release_name, entry_line, pr_added_text[entry_line]
|
|
issue_count++
|
|
continue
|
|
}
|
|
if (section_line == 0) {
|
|
printf "CHANGELOG.md entry must be inside a subsection (### ...): line %d: %s\n", entry_line, pr_added_text[entry_line]
|
|
issue_count++
|
|
continue
|
|
}
|
|
|
|
section_name = changelog[section_line]
|
|
next_heading = file_line_count + 1
|
|
for (i = entry_line + 1; i <= file_line_count; i++) {
|
|
if (changelog[i] ~ /^### / || changelog[i] ~ /^## /) {
|
|
next_heading = i
|
|
break
|
|
}
|
|
}
|
|
|
|
for (i = entry_line + 1; i < next_heading; i++) {
|
|
line_text = changelog[i]
|
|
if (line_text ~ /^[[:space:]]*$/) {
|
|
continue
|
|
}
|
|
if (i in added) {
|
|
continue
|
|
}
|
|
printf "CHANGELOG.md PR-linked entry must be appended at the end of section %s: line %d: %s\n", section_name, entry_line, pr_added_text[entry_line]
|
|
printf "Found existing non-added line below it at line %d: %s\n", i, line_text
|
|
issue_count++
|
|
break
|
|
}
|
|
}
|
|
|
|
if (issue_count > 0) {
|
|
print "Move this PR changelog entry to the end of its section (just before the next heading)."
|
|
exit 1
|
|
}
|
|
}
|
|
' "$diff_file" CHANGELOG.md; then
|
|
rm -f "$diff_file"
|
|
exit 1
|
|
fi
|
|
rm -f "$diff_file"
|
|
echo "changelog placement validated: PR-linked entries are appended at section tail"
|
|
|
|
if changelog_thanks_required_for_contributor "$contrib"; then
|
|
local with_pr_and_thanks
|
|
with_pr_and_thanks=$(printf '%s\n' "$added_lines" | grep -Ein "$pr_pattern" | grep -Fi "thanks @$contrib" || true)
|
|
if [ -z "$with_pr_and_thanks" ]; then
|
|
echo "CHANGELOG.md update must include both PR #$pr and thanks @$contrib on the changelog entry line."
|
|
exit 1
|
|
fi
|
|
echo "changelog validated: found PR #$pr + thanks @$contrib"
|
|
return 0
|
|
fi
|
|
|
|
if ! changelog_explicit_human_thanks_required_for_contributor "$contrib"; then
|
|
echo "changelog validated: found PR #$pr (no eligible human contributor handle, skipping thanks check)"
|
|
return 0
|
|
fi
|
|
|
|
local with_pr_and_any_thanks
|
|
with_pr_and_any_thanks=$(printf '%s\n' "$added_lines" | grep -Ein "$pr_pattern" | grep -Ei '(^|[[:space:]])thanks[[:space:]]+@' || true)
|
|
if [ -z "$with_pr_and_any_thanks" ]; then
|
|
echo "CHANGELOG.md update for bot/app/non-creditable author $contrib must include an explicit human Thanks @handle on the PR #$pr entry line."
|
|
echo "Choose the credited original contributor, or stop for maintainer input if authorship is unclear."
|
|
exit 1
|
|
fi
|
|
|
|
echo "changelog validated: found PR #$pr + explicit thanks for bot/app/non-creditable author $contrib"
|
|
}
|
|
|
|
validate_changelog_merge_hygiene() {
|
|
local diff
|
|
diff=$(pr_git diff --unified=0 "$PR_MAIN_SHA...HEAD" -- CHANGELOG.md)
|
|
|
|
local removed_lines
|
|
removed_lines=$(printf '%s\n' "$diff" | awk '
|
|
/^---/ { next }
|
|
/^-/ { print substr($0, 2) }
|
|
')
|
|
if [ -z "$removed_lines" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local removed_refs
|
|
removed_refs=$(printf '%s\n' "$removed_lines" | grep -Eo '#[0-9]+' | sort -u || true)
|
|
if [ -z "$removed_refs" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local added_lines
|
|
added_lines=$(printf '%s\n' "$diff" | awk '
|
|
/^\+\+\+/ { next }
|
|
/^\+/ { print substr($0, 2) }
|
|
')
|
|
|
|
local ref
|
|
while IFS= read -r ref; do
|
|
[ -z "$ref" ] && continue
|
|
if ! printf '%s\n' "$added_lines" | grep -Fq "$ref"; then
|
|
echo "CHANGELOG.md drops existing entry reference $ref without re-adding it."
|
|
echo "Likely merge conflict loss; restore the dropped entry (or keep the same PR ref in rewritten text)."
|
|
exit 1
|
|
fi
|
|
done <<<"$removed_refs"
|
|
|
|
echo "changelog merge hygiene validated: no dropped PR references"
|
|
}
|