forked from heavy-duty/ceremony
Compare commits
No commits in common. "e5ebbf57fb16c43dcc2e0b120a516edd7dadbb84" and "1f5dd39a986f5e7256819051c42063c1af657700" have entirely different histories.
e5ebbf57fb
...
1f5dd39a98
4 changed files with 4 additions and 136 deletions
|
|
@ -1,3 +0,0 @@
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Forgejo drafts and fast-forward conflict-check windows no longer surface as merge conflicts when the API has not distinguished one (#236).
|
|
||||||
|
|
@ -429,12 +429,9 @@ forge_pr_view() {
|
||||||
status="$(forge_api "repos/$REPO/commits/$sha/status")" || return 1
|
status="$(forge_api "repos/$REPO/commits/$sha/status")" || return 1
|
||||||
jq -n --argjson pr "$pr" --argjson st "$status" '
|
jq -n --argjson pr "$pr" --argjson st "$status" '
|
||||||
{
|
{
|
||||||
# Forgejo folds checking, conflict, check error, and WIP into false.
|
mergeable: (if $pr.mergeable == true then "MERGEABLE"
|
||||||
# Draft must win because WIP makes the boolean carry no merge result (#236).
|
elif $pr.mergeable == false then "CONFLICTING"
|
||||||
mergeable: (if $pr.draft == true then "UNKNOWN"
|
else "UNKNOWN" end),
|
||||||
elif $pr.mergeable == true then "MERGEABLE"
|
|
||||||
elif $pr.merge_base == $pr.base.sha then "UNKNOWN"
|
|
||||||
else "CONFLICTING" end),
|
|
||||||
statusCheckRollup: [
|
statusCheckRollup: [
|
||||||
$st.statuses[]? | {
|
$st.statuses[]? | {
|
||||||
__typename: "StatusContext",
|
__typename: "StatusContext",
|
||||||
|
|
|
||||||
|
|
@ -599,7 +599,7 @@ pr_view_stub() {
|
||||||
printf 'HTTP/1.1 200 OK\r\nX-Total-Count: 1\r\n\r\n' >"$hdr"
|
printf 'HTTP/1.1 200 OK\r\nX-Total-Count: 1\r\n\r\n' >"$hdr"
|
||||||
case "$url" in
|
case "$url" in
|
||||||
*/status) printf '%s' "$FAKE_STATUS" >"$out" ;;
|
*/status) printf '%s' "$FAKE_STATUS" >"$out" ;;
|
||||||
*) printf '%s' "$FAKE_PR" >"$out" ;;
|
*) printf '{"head":{"sha":"abc"},"mergeable":true}' >"$out" ;;
|
||||||
esac
|
esac
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -608,7 +608,6 @@ pr_view_stub() {
|
||||||
FAKE_STATUS='{"state":"failure","statuses":[
|
FAKE_STATUS='{"state":"failure","statuses":[
|
||||||
{"context":"ci / check","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"},
|
{"context":"ci / check","status":"success","created_at":"2026-08-02T10:00:00Z","updated_at":"2026-08-02T10:00:00Z"},
|
||||||
{"context":"ci / check","status":"failure","created_at":"2026-08-02T09:00:00Z","updated_at":"2026-08-02T09:00:00Z"}]}'
|
{"context":"ci / check","status":"failure","created_at":"2026-08-02T09:00:00Z","updated_at":"2026-08-02T09:00:00Z"}]}'
|
||||||
FAKE_PR='{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"ancestor","draft":false,"mergeable":true}'
|
|
||||||
pr_view_stub
|
pr_view_stub
|
||||||
view_json="$(REPO=o/r forge_pr_view 5)"
|
view_json="$(REPO=o/r forge_pr_view 5)"
|
||||||
check "pr_view maps createdAt" 0 "" \
|
check "pr_view maps createdAt" 0 "" \
|
||||||
|
|
@ -617,31 +616,6 @@ check "pr_view maps completedAt" 0 "" \
|
||||||
grep -q '"completedAt":' <<<"$view_json"
|
grep -q '"completedAt":' <<<"$view_json"
|
||||||
check "pr_view maps mergeable to the UI string" 0 "" \
|
check "pr_view maps mergeable to the UI string" 0 "" \
|
||||||
grep -q '"mergeable": "MERGEABLE"' <<<"$view_json"
|
grep -q '"mergeable": "MERGEABLE"' <<<"$view_json"
|
||||||
|
|
||||||
# Forgejo folds conflict-checking, conflict-check errors, real conflicts, and
|
|
||||||
# WIP into one false boolean. These fixtures pin the distinctions the API
|
|
||||||
# object still lets the backend report honestly (#236).
|
|
||||||
FAKE_PR='{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"ancestor","draft":true,"mergeable":false}'
|
|
||||||
view_json="$(REPO=o/r forge_pr_view 5)"
|
|
||||||
check "pr_view reports draft mergeability as unknown" 0 "" \
|
|
||||||
grep -q '"mergeable": "UNKNOWN"' <<<"$view_json"
|
|
||||||
|
|
||||||
FAKE_PR='{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"base","draft":false,"mergeable":false}'
|
|
||||||
view_json="$(REPO=o/r forge_pr_view 5)"
|
|
||||||
check "pr_view reports a fast-forward false mergeability as unknown" 0 "" \
|
|
||||||
grep -q '"mergeable": "UNKNOWN"' <<<"$view_json"
|
|
||||||
|
|
||||||
FAKE_PR='{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"ancestor","draft":false,"mergeable":false}'
|
|
||||||
view_json="$(REPO=o/r forge_pr_view 5)"
|
|
||||||
check "pr_view preserves a distinguishable real conflict" 0 "" \
|
|
||||||
grep -q '"mergeable": "CONFLICTING"' <<<"$view_json"
|
|
||||||
|
|
||||||
# Unreachable on Forgejo: Mergeable() includes !IsWorkInProgress(). This
|
|
||||||
# ordering fixture exists only to pin draft ahead of mergeable true (#236).
|
|
||||||
FAKE_PR='{"head":{"sha":"abc"},"base":{"sha":"base"},"merge_base":"ancestor","draft":true,"mergeable":true}'
|
|
||||||
view_json="$(REPO=o/r forge_pr_view 5)"
|
|
||||||
check "pr_view reads draft before the mergeable boolean" 0 "" \
|
|
||||||
grep -q '"mergeable": "UNKNOWN"' <<<"$view_json"
|
|
||||||
# The real proof: feed it to the production classifier and confirm the newer
|
# The real proof: feed it to the production classifier and confirm the newer
|
||||||
# SUCCESS wins over the older FAILURE regardless of array order.
|
# SUCCESS wins over the older FAILURE regardless of array order.
|
||||||
# shellcheck source=actions/labels-reconcile/labels-reconcile.sh
|
# shellcheck source=actions/labels-reconcile/labels-reconcile.sh
|
||||||
|
|
|
||||||
|
|
@ -172,20 +172,6 @@ $BOT2
|
||||||
$BOT3" REVIEWS_JSON='[]'
|
$BOT3" REVIEWS_JSON='[]'
|
||||||
expect "requested bots mean bots-reviewing" state:bots-reviewing "$(decide_state)"
|
expect "requested bots mean bots-reviewing" state:bots-reviewing "$(decide_state)"
|
||||||
|
|
||||||
# Forgejo materializes each live request as a REQUEST_REVIEW row. Those rows
|
|
||||||
# are not submitted verdicts (#235): they must leave all three logins
|
|
||||||
# outstanding, so an opening round stays with the panel rather than falling
|
|
||||||
# through to the builder as three comment-only answers.
|
|
||||||
REVIEWS_JSON="$(reviews \
|
|
||||||
"$(rev "$BOT1" REQUEST_REVIEW "" "" t1)" \
|
|
||||||
"$(rev "$BOT2" REQUEST_REVIEW "" "" t2)" \
|
|
||||||
"$(rev "$BOT3" REQUEST_REVIEW "" "" t3)")"
|
|
||||||
REQUESTED="$(outstanding_requests "$BOT1
|
|
||||||
$BOT2
|
|
||||||
$BOT3")"
|
|
||||||
expect "three Forgejo request rows keep the opening round with the panel" \
|
|
||||||
state:bots-reviewing "$(round_state)"
|
|
||||||
|
|
||||||
# -- a bot that never reviewed keeps the round open ---------------------------
|
# -- a bot that never reviewed keeps the round open ---------------------------
|
||||||
# With a live request that is the bots' ball; with NO request outstanding it
|
# With a live request that is the bots' ball; with NO request outstanding it
|
||||||
# is the agent's, because nothing is coming until somebody asks.
|
# is the agent's, because nothing is coming until somebody asks.
|
||||||
|
|
@ -229,25 +215,6 @@ REVIEWS_JSON="$(reviews \
|
||||||
"$(rev "$BOT3" APPROVED head1 "" t3)")"
|
"$(rev "$BOT3" APPROVED head1 "" t3)")"
|
||||||
expect "changes-requested blocks even from an old head" state:addressing "$(decide_state)"
|
expect "changes-requested blocks even from an old head" state:addressing "$(decide_state)"
|
||||||
|
|
||||||
# bot_verdict grades submitted states from both forges (#235). Each direct
|
|
||||||
# assertion names one arm so a later vocabulary regression cannot hide behind
|
|
||||||
# round_state's shared BLOCK/FEEDBACK handling.
|
|
||||||
expect "GitHub CHANGES_REQUESTED grades as a block" BLOCK \
|
|
||||||
"$(bot_verdict "$BOT1")"
|
|
||||||
REVIEWS_JSON="$(reviews "$(rev "$BOT1" REQUEST_CHANGES old1 "blockers below" t1)")"
|
|
||||||
expect "Forgejo REQUEST_CHANGES grades as a block" BLOCK \
|
|
||||||
"$(bot_verdict "$BOT1")"
|
|
||||||
REVIEWS_JSON="$(reviews "$(rev "$BOT1" COMMENT head1 "non-blocking note" t1)")"
|
|
||||||
expect "Forgejo COMMENT grades as feedback" FEEDBACK \
|
|
||||||
"$(bot_verdict "$BOT1")"
|
|
||||||
REVIEWS_JSON="$(reviews "$(rev "$BOT1" FUTURE_FORGE_STATE head1 "" t1)")"
|
|
||||||
bot_verdict "$BOT1" >"$RTMP/unknown-verdict" 2>"$RTMP/unknown-verdict-log"
|
|
||||||
expect "an unrecognised review state is conservatively missing" MISSING \
|
|
||||||
"$(cat "$RTMP/unknown-verdict")"
|
|
||||||
expect "an unrecognised review state logs the login and spelling" yes \
|
|
||||||
"$(grep -qF "$BOT1: unrecognised review state FUTURE_FORGE_STATE" \
|
|
||||||
"$RTMP/unknown-verdict-log" && echo yes || echo no)"
|
|
||||||
|
|
||||||
# -- a stale approval must not promote unreviewed code ------------------------
|
# -- a stale approval must not promote unreviewed code ------------------------
|
||||||
REVIEWS_JSON="$(reviews \
|
REVIEWS_JSON="$(reviews \
|
||||||
"$(rev "$BOT1" APPROVED old1 "" t1)" \
|
"$(rev "$BOT1" APPROVED old1 "" t1)" \
|
||||||
|
|
@ -286,24 +253,6 @@ REQUESTED="$HUMAN"
|
||||||
expect "re-requested human is needs-human again" state:needs-human "$(decide_state)"
|
expect "re-requested human is needs-human again" state:needs-human "$(decide_state)"
|
||||||
REQUESTED=""
|
REQUESTED=""
|
||||||
|
|
||||||
# Forgejo's human-block spelling carries the same meaning (#235). This is
|
|
||||||
# independently observable because only BLOCK prevents state:needs-human once
|
|
||||||
# every bot approves.
|
|
||||||
REVIEWS_JSON="$(reviews \
|
|
||||||
"$(rev "$BOT1" APPROVED head1 "" t1)" \
|
|
||||||
"$(rev "$BOT2" APPROVED head1 "" t2)" \
|
|
||||||
"$(rev "$BOT3" APPROVED head1 "" t3)" \
|
|
||||||
"$(rev "$HUMAN" REQUEST_CHANGES head1 "not yet" t4)")"
|
|
||||||
expect "Forgejo human request-changes with bots approving is addressing" \
|
|
||||||
state:addressing "$(decide_state)"
|
|
||||||
REVIEWS_JSON="$(reviews \
|
|
||||||
"$(rev "$BOT1" APPROVED head1 "" t1)" \
|
|
||||||
"$(rev "$BOT2" APPROVED head1 "" t2)" \
|
|
||||||
"$(rev "$BOT3" APPROVED head1 "" t3)" \
|
|
||||||
"$(rev "$HUMAN" APPROVED head1 "" t4)")"
|
|
||||||
expect "the same Forgejo-shaped fixture with human approval reaches needs-human" \
|
|
||||||
state:needs-human "$(decide_state)"
|
|
||||||
|
|
||||||
# -- an old human comment must not wedge the handoff (codex, #85 round 3) -----
|
# -- an old human comment must not wedge the handoff (codex, #85 round 3) -----
|
||||||
REVIEWS_JSON="$(reviews \
|
REVIEWS_JSON="$(reviews \
|
||||||
"$(rev "$HUMAN" COMMENTED old1 "early thoughts" t0)" \
|
"$(rev "$HUMAN" COMMENTED old1 "early thoughts" t0)" \
|
||||||
|
|
@ -1639,46 +1588,6 @@ expect "...with no 'reconciled.' token in the output" \
|
||||||
expect "...naming the attempt that did not happen" \
|
expect "...naming the attempt that did not happen" \
|
||||||
yes "$(grep -q 'label edit FAILED' <<<"$sf_out" && echo yes || echo no)"
|
yes "$(grep -q 'label edit FAILED' <<<"$sf_out" && echo yes || echo no)"
|
||||||
|
|
||||||
# Drive the ingestion expression through main(), independently of
|
|
||||||
# bot_verdict (#235). Capturing REVIEWS_JSON at the outstanding_requests
|
|
||||||
# boundary proves REQUEST_REVIEW never reaches the grader; the APPROVED
|
|
||||||
# control proves the filter did not simply discard every row.
|
|
||||||
review_filter_probe() {
|
|
||||||
(
|
|
||||||
REPO=owner/repo
|
|
||||||
LABELS_CONF="$FIXTURE_CONF"
|
|
||||||
CEREMONY_FORGE=github
|
|
||||||
# shellcheck disable=SC2317 # reached through the forge backend, not called directly (#188)
|
|
||||||
gh() {
|
|
||||||
if [ "$1" = label ] && [ "$2" = list ]; then core_label_rows | cut -d'|' -f1; return 0; fi
|
|
||||||
if [ "$1" = pr ] && [ "$2" = list ]; then printf '%s\n' 601; return 0; fi
|
|
||||||
if [ "$1" = pr ] && [ "$2" = view ]; then
|
|
||||||
jq -n '{mergeable:"MERGEABLE",statusCheckRollup:[]}'
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ "$1" = issue ] && [ "$2" = edit ]; then return 0; fi
|
|
||||||
case "$(forge_stub_path "$*")" in
|
|
||||||
*repos/owner/repo/pulls/601/reviews*)
|
|
||||||
jq -nc \
|
|
||||||
'{user:{login:"fixture-bot-one"},state:"REQUEST_REVIEW",commit_id:"",submitted_at:"2026-08-22T00:46:05Z"},
|
|
||||||
{user:{login:"fixture-bot-two"},state:"APPROVED",commit_id:"head1",submitted_at:"2026-08-22T00:47:05Z"}' ;;
|
|
||||||
*/pulls/601)
|
|
||||||
jq -n '{draft:true,user:{login:"fixture-builder"},head:{sha:"head1"},base:{sha:"base1"},
|
|
||||||
labels:[{name:"state:building"}],requested_reviewers:[],
|
|
||||||
created_at:"2026-08-22T00:45:00Z"}' ;;
|
|
||||||
*) printf '[]\n' ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
outstanding_requests() {
|
|
||||||
printf '%s\n' "$REVIEWS_JSON" >"$RTMP/gradeable-reviews.json"
|
|
||||||
}
|
|
||||||
main >/dev/null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
review_filter_probe
|
|
||||||
expect "REQUEST_REVIEW is removed before REVIEWS_JSON reaches the grader" \
|
|
||||||
APPROVED "$(jq -r 'map(.state) | join(",")' "$RTMP/gradeable-reviews.json")"
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# outstanding_requests — the portable "who still owes a verdict" (#188 term 4)
|
# outstanding_requests — the portable "who still owes a verdict" (#188 term 4)
|
||||||
#
|
#
|
||||||
|
|
@ -1706,15 +1615,6 @@ expect "a stale approval still owes a verdict" "$BOT3" \
|
||||||
expect "a reviewer who never reviewed still owes one" "nobody" \
|
expect "a reviewer who never reviewed still owes one" "nobody" \
|
||||||
"$(outstanding_requests "nobody")"
|
"$(outstanding_requests "nobody")"
|
||||||
|
|
||||||
REVIEWS_JSON="$(reviews \
|
|
||||||
"$(rev "$BOT1" REQUEST_REVIEW "" "" 2026-08-22T00:46:05Z)")"
|
|
||||||
expect "a Forgejo request row is not an answer and leaves the login outstanding" \
|
|
||||||
"$BOT1" "$(outstanding_requests "$BOT1")"
|
|
||||||
REVIEWS_JSON="$(reviews \
|
|
||||||
"$(rev "$BOT1" APPROVED head1 "" 2026-08-01T00:00:00Z)" \
|
|
||||||
"$(rev "$BOT2" CHANGES_REQUESTED head1 "" 2026-08-01T00:00:00Z)" \
|
|
||||||
"$(rev "$BOT3" APPROVED head0 "" 2026-07-01T00:00:00Z)")"
|
|
||||||
|
|
||||||
# The Forgejo shape, end to end: the field lists all three long after every
|
# The Forgejo shape, end to end: the field lists all three long after every
|
||||||
# verdict landed. Only the stale one may survive the filter.
|
# verdict landed. Only the stale one may survive the filter.
|
||||||
expect "the never-cleared forgejo field collapses to who actually owes" \
|
expect "the never-cleared forgejo field collapses to who actually owes" \
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue