Merge pull request 'lib/forge-*.sh + labels-reconcile — forge_commit_at, because Forgejo serves a single commit at /git/commits/{sha} (#209)' (#212) from build/209-commit-at into main
Some checks failed
release / release (push) Waiting to run
CI / test (push) Has been cancelled
CI / release-exercise (push) Has been cancelled
CI / self-guards (push) Has been cancelled
CI / action-exercise (push) Has been cancelled
CI / docs-sync-exercise (push) Has been cancelled
Some checks failed
release / release (push) Waiting to run
CI / test (push) Has been cancelled
CI / release-exercise (push) Has been cancelled
CI / self-guards (push) Has been cancelled
CI / action-exercise (push) Has been cancelled
CI / docs-sync-exercise (push) Has been cancelled
Reviewed-on: #212 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders>
This commit is contained in:
commit
0cb320b807
5 changed files with 75 additions and 2 deletions
|
|
@ -1094,8 +1094,8 @@ main() {
|
|||
HEAD_COMMIT_AT=""
|
||||
if [ "$DRAFT" != true ]; then
|
||||
HEAD_COMMIT_ERR_FILE="$(mktemp)"
|
||||
HEAD_COMMIT_AT="$(forge_api "repos/$REPO/commits/$HEAD_SHA" \
|
||||
--jq '.commit.committer.date' 2>"$HEAD_COMMIT_ERR_FILE" || echo "")"
|
||||
HEAD_COMMIT_AT="$(forge_commit_at "$HEAD_SHA" \
|
||||
2>"$HEAD_COMMIT_ERR_FILE" || echo "")"
|
||||
HEAD_COMMIT_ERR="$(cat "$HEAD_COMMIT_ERR_FILE")"
|
||||
rm -f "$HEAD_COMMIT_ERR_FILE"
|
||||
case "$HEAD_COMMIT_AT" in
|
||||
|
|
|
|||
16
changelog.d/209.md
Normal file
16
changelog.d/209.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
### Fixed
|
||||
|
||||
- `blocker:unrequested` is judged on this forge again. The head-commit date was
|
||||
read from `repos/{o}/{r}/commits/{sha}`, which Forgejo answers **404** — so
|
||||
every sweep degraded and left the blocker unjudged (#209).
|
||||
|
||||
- `forge_commit_at` is a verb on both backends: GitHub serves a single commit at
|
||||
the bare path with the date nested, Forgejo at `git/commits/{sha}` with it
|
||||
under `.created`. The caller asks for one timestamp and knows neither shape
|
||||
(#209).
|
||||
|
||||
### Added
|
||||
|
||||
- `test/forge-backends.test.sh` pins each backend's path **and** field, because
|
||||
a stubbed `forge_api` cannot catch a wrong path — which is how this shipped
|
||||
and why a live sweep was what found it (#209).
|
||||
|
|
@ -642,6 +642,27 @@ forge_release_exists() {
|
|||
# forge_commit_pulls <sha> — the pull requests whose merge produced <sha>, as
|
||||
# a JSON ARRAY in GitHub's shape. An empty array is a completed read that
|
||||
# found nothing; a non-zero exit is a read that did not complete.
|
||||
# forge_commit_at <sha> — the commit's committer date, ISO-8601, or empty.
|
||||
#
|
||||
# THE FOURTH ASYMMETRY (#209), measured 2026-08-05:
|
||||
#
|
||||
# GET /repos/{o}/{r}/commits/{sha} -> 404 (200 on GitHub)
|
||||
# GET /repos/{o}/{r}/git/commits/{sha} -> 200 date under `.created`
|
||||
#
|
||||
# Found by the first post-merge sweep after the 0.6.0 merge, not by review:
|
||||
# #198 ported this call site onto the shim with GitHub's path unchanged, and
|
||||
# the block it lives in had never executed here before. Every sweep printed
|
||||
# `could not read the head commit's date` and left blocker:unrequested
|
||||
# unjudged.
|
||||
#
|
||||
# `.created` and not `.commit.committer.date`: the /git/commits payload is the
|
||||
# git object, whose top-level `created` is the committer date. The verb hides
|
||||
# both differences so the caller keeps asking for one timestamp.
|
||||
forge_commit_at() {
|
||||
local sha="${1:?forge_commit_at: sha required}"
|
||||
forge_api "repos/$REPO/git/commits/$sha" --jq '.created'
|
||||
}
|
||||
|
||||
forge_commit_pulls() {
|
||||
local sha="${1:?forge_commit_pulls: sha required}" body code out
|
||||
body="$(mktemp)"
|
||||
|
|
|
|||
|
|
@ -186,6 +186,18 @@ forge_release_exists() {
|
|||
# forge_commit_pulls <sha> — the pull requests whose merge produced <sha>, as
|
||||
# a JSON array. GitHub serves the array directly; the forgejo twin builds
|
||||
# one from its single-object endpoint so this call site is identical.
|
||||
# forge_commit_at <sha> — the commit's committer date, ISO-8601, or empty.
|
||||
#
|
||||
# A VERB rather than a path at the call site, because the two forges do not
|
||||
# agree on where a single commit lives: GitHub serves it at /commits/{sha},
|
||||
# Forgejo 404s there and serves it at /git/commits/{sha} with the timestamp
|
||||
# under a different field (#209). The caller wants one timestamp; it should not
|
||||
# have to know either shape.
|
||||
forge_commit_at() {
|
||||
local sha="${1:?forge_commit_at: sha required}"
|
||||
forge_api "repos/$REPO/commits/$sha" --jq '.commit.committer.date'
|
||||
}
|
||||
|
||||
forge_commit_pulls() {
|
||||
local sha="${1:?forge_commit_pulls: sha required}" errf out rc err
|
||||
errf="$(mktemp)"
|
||||
|
|
|
|||
|
|
@ -775,6 +775,21 @@ writes_after() { "$@" >/dev/null 2>&1; cat "$WRITES"; }
|
|||
repo_empty_release() { REPO='' forge_release_exists 1.2.3; }
|
||||
repo_empty_pulls() { REPO='' forge_commit_pulls deadbeef; }
|
||||
|
||||
# forge_commit_at — the FOURTH asymmetry (#209). Forgejo 404s on /commits/{sha}
|
||||
# and serves the object at /git/commits/{sha}, with the committer date under
|
||||
# `.created` rather than `.commit.committer.date`. A stubbed forge_api cannot
|
||||
# catch a wrong PATH, which is how #198 shipped GitHub's path here and every
|
||||
# sweep printed `could not read the head commit's date`.
|
||||
release_stub 200 '{"created":"2026-08-05T13:11:33Z","commit":{"committer":{"date":"WRONG"}}}'
|
||||
check "forgejo: the commit date comes from .created" 0 "2026-08-05T13:11:33Z" \
|
||||
forge_commit_at deadbeef
|
||||
fj_not_nested() { ! forge_commit_at deadbeef | grep -q WRONG; }
|
||||
check "...and never from GitHub's nested field" 0 "" fj_not_nested
|
||||
check "forgejo: it asks /git/commits/{sha}" 0 "git/commits/deadbeef" \
|
||||
writes_after forge_commit_at deadbeef
|
||||
fj_not_bare_path() { ! grep -qE 'repos/o/r/commits/deadbeef( |$)' "$WRITES"; }
|
||||
check "...and never the bare /commits/{sha}, which 404s here" 0 "" fj_not_bare_path
|
||||
|
||||
release_stub 200 '{"number":7,"merged_at":"2026-01-01T00:00:00Z","labels":[{"name":"release"}]}'
|
||||
check "forgejo: one PR object becomes a one-element array" 0 '"number":7' \
|
||||
forge_commit_pulls deadbeef
|
||||
|
|
@ -857,5 +872,14 @@ check "github: the tag goes to /git/refs" 0 "git/refs" \
|
|||
gh_after forge_tag_create 1.2.3 cafebabe
|
||||
check "github: PRs behind a commit use the PLURAL path" 0 "commits/deadbeef/pulls" \
|
||||
gh_after forge_commit_pulls deadbeef
|
||||
# The other half of #209's asymmetry: GitHub serves a single commit at the bare
|
||||
# path, with the date nested. Swapping the two backends' paths must red one of
|
||||
# these two files, which is the whole point of pinning both.
|
||||
check "github: a single commit is the BARE path" 0 "commits/deadbeef" \
|
||||
gh_after forge_commit_at deadbeef
|
||||
gh_not_git_commits() { ! gh_after forge_commit_at deadbeef | grep -q 'git/commits'; }
|
||||
check "...and never Forgejo's git/commits" 0 "" gh_not_git_commits
|
||||
check "...reading the nested committer date" 0 "commit.committer.date" \
|
||||
gh_after forge_commit_at deadbeef
|
||||
|
||||
summary
|
||||
|
|
|
|||
Loading…
Reference in a new issue