mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-30 07:33:59 +08:00
Signed-off-by: John Myers <johntmyers@users.noreply.github.com> Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
472 lines
16 KiB
Bash
Executable File
472 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat >&2 <<'EOF'
|
|
Usage:
|
|
review-feedback-ledger OWNER REPO PR_NUMBER
|
|
review-feedback-ledger --input RAW_LEDGER_INPUT.json
|
|
EOF
|
|
}
|
|
|
|
collect_live_input() {
|
|
[[ "$#" -eq 3 ]] || {
|
|
usage
|
|
return 2
|
|
}
|
|
|
|
local owner="$1"
|
|
local repo="$2"
|
|
local pr_number="$3"
|
|
[[ "$owner" =~ ^[A-Za-z0-9_.-]+$ ]] || {
|
|
echo "invalid repository owner" >&2
|
|
return 2
|
|
}
|
|
[[ "$repo" =~ ^[A-Za-z0-9_.-]+$ ]] || {
|
|
echo "invalid repository name" >&2
|
|
return 2
|
|
}
|
|
[[ "$pr_number" =~ ^[0-9]+$ ]] || {
|
|
echo "invalid PR number" >&2
|
|
return 2
|
|
}
|
|
|
|
local tmp cleanup_cmd
|
|
tmp="$(mktemp -d)"
|
|
printf -v cleanup_cmd 'rm -rf -- %q' "$tmp"
|
|
trap "$cleanup_cmd" RETURN
|
|
|
|
gh api graphql --paginate \
|
|
-f owner="$owner" \
|
|
-f repo="$repo" \
|
|
-F number="$pr_number" \
|
|
-f query='
|
|
query(
|
|
$owner: String!
|
|
$repo: String!
|
|
$number: Int!
|
|
$endCursor: String
|
|
) {
|
|
repository(owner: $owner, name: $repo) {
|
|
pullRequest(number: $number) {
|
|
author {
|
|
login
|
|
}
|
|
headRefOid
|
|
baseRefOid
|
|
reviewThreads(first: 100, after: $endCursor) {
|
|
nodes {
|
|
id
|
|
isResolved
|
|
isOutdated
|
|
path
|
|
line
|
|
resolvedBy {
|
|
login
|
|
}
|
|
comments(first: 100) {
|
|
nodes {
|
|
databaseId
|
|
author {
|
|
login
|
|
}
|
|
authorAssociation
|
|
body
|
|
createdAt
|
|
updatedAt
|
|
url
|
|
commit {
|
|
oid
|
|
}
|
|
pullRequestReview {
|
|
id
|
|
}
|
|
replyTo {
|
|
databaseId
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pageInfo {
|
|
hasNextPage
|
|
endCursor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}' > "$tmp/thread-pages.json"
|
|
|
|
gh api "repos/$owner/$repo/pulls/$pr_number/reviews?per_page=100" \
|
|
--paginate > "$tmp/review-pages.json"
|
|
gh api "repos/$owner/$repo/issues/$pr_number/comments?per_page=100" \
|
|
--paginate > "$tmp/issue-comment-pages.json"
|
|
|
|
local head_sha base_sha merge_base_sha patch_id
|
|
head_sha="$(jq -r \
|
|
'.data.repository.pullRequest.headRefOid // empty' \
|
|
"$tmp/thread-pages.json" | head -n 1)"
|
|
base_sha="$(jq -r \
|
|
'.data.repository.pullRequest.baseRefOid // empty' \
|
|
"$tmp/thread-pages.json" | head -n 1)"
|
|
[[ -n "$head_sha" && -n "$base_sha" ]] || {
|
|
echo "pull request tree identity missing from GitHub response" >&2
|
|
return 1
|
|
}
|
|
merge_base_sha="$(gh api \
|
|
"repos/$owner/$repo/compare/$base_sha...$head_sha" \
|
|
--jq '.merge_base_commit.sha')"
|
|
# GitHub's full-diff endpoint rejects large PRs. Fetch only the two
|
|
# immutable trees needed for patch identity; never check out PR code.
|
|
# Patch identity is optional: failure disables equivalence shortcuts,
|
|
# not the mandatory feedback ledger or its exact-SHA review history.
|
|
patch_id=""
|
|
if [[ "$merge_base_sha" =~ ^[0-9a-f]{40}$ && "$head_sha" =~ ^[0-9a-f]{40}$ ]] &&
|
|
git init --bare --quiet "$tmp/trees.git" &&
|
|
GIT_TERMINAL_PROMPT=0 git -C "$tmp/trees.git" \
|
|
-c credential.helper='!gh auth git-credential' \
|
|
fetch --quiet --no-tags --no-recurse-submodules --depth=1 \
|
|
"https://github.com/$owner/$repo.git" "$merge_base_sha" "$head_sha" &&
|
|
patch_id="$(
|
|
git -C "$tmp/trees.git" diff --no-ext-diff --no-textconv \
|
|
--no-renames "$merge_base_sha" "$head_sha" -- |
|
|
git patch-id --stable |
|
|
awk 'NR == 1 { print $1 }'
|
|
)"; then
|
|
:
|
|
else
|
|
patch_id=""
|
|
echo "patch identity unavailable; rebase equivalence disabled" >&2
|
|
fi
|
|
|
|
jq -n \
|
|
--arg head_sha "$head_sha" \
|
|
--arg base_sha "$base_sha" \
|
|
--arg merge_base_sha "$merge_base_sha" \
|
|
--arg patch_id "$patch_id" \
|
|
'{
|
|
head_sha: $head_sha,
|
|
base_sha: $base_sha,
|
|
merge_base_sha: $merge_base_sha,
|
|
patch_id: (if $patch_id == "" then null else $patch_id end)
|
|
}' > "$tmp/current-tree.json"
|
|
|
|
jq -n \
|
|
--slurpfile thread_pages "$tmp/thread-pages.json" \
|
|
--slurpfile review_pages "$tmp/review-pages.json" \
|
|
--slurpfile issue_comment_pages "$tmp/issue-comment-pages.json" \
|
|
--slurpfile current_tree "$tmp/current-tree.json" \
|
|
'{
|
|
thread_pages: $thread_pages,
|
|
review_pages: $review_pages,
|
|
issue_comment_pages: $issue_comment_pages,
|
|
current_tree: $current_tree[0]
|
|
}'
|
|
}
|
|
|
|
read_input() {
|
|
if [[ "${1:-}" == "--input" ]]; then
|
|
[[ "$#" -eq 2 && -r "$2" ]] || {
|
|
usage
|
|
return 2
|
|
}
|
|
cat "$2"
|
|
return
|
|
fi
|
|
|
|
collect_live_input "$@"
|
|
}
|
|
|
|
read_input "$@" | jq '
|
|
def thread_pull_request:
|
|
.data.repository.pullRequest;
|
|
def is_gator_body:
|
|
startswith("> **gator-agent**");
|
|
def explicit_finding_ids:
|
|
[scan("GATOR-[0-9A-Fa-f]{8}-[0-9]{2}")] | unique;
|
|
def marked_head_sha:
|
|
([capture("Head SHA: `?(?<sha>[0-9A-Fa-f]{40})`?").sha][0] // null);
|
|
def marked_sha($field):
|
|
([capture($field + ": `?(?<sha>[0-9A-Fa-f]{40})`?").sha][0] // null);
|
|
def marked_patch_id:
|
|
([capture("Patch ID: `?(?<id>[0-9A-Fa-f]{40})`?").id][0] // null);
|
|
def marked_payload_version:
|
|
([capture("Gator payload: `?(?<version>[0-9]+)`?").version | tonumber][0] // null);
|
|
def is_code_review_body:
|
|
contains("## PR Review Status") or contains("## Re-check After");
|
|
|
|
if has("thread_pages") then
|
|
.
|
|
else
|
|
{
|
|
thread_pages: [.],
|
|
review_pages: [],
|
|
issue_comment_pages: [],
|
|
current_tree: null
|
|
}
|
|
end
|
|
| {
|
|
schema_version: 4,
|
|
pr_author: ([.thread_pages[] | thread_pull_request.author.login][0] // null),
|
|
current_head_sha: (
|
|
[.thread_pages[] | thread_pull_request.headRefOid]
|
|
| map(select(. != null))
|
|
| .[0] // null
|
|
),
|
|
current_base_sha: (
|
|
.current_tree.base_sha //
|
|
([.thread_pages[] | thread_pull_request.baseRefOid]
|
|
| map(select(. != null))
|
|
| .[0] // null)
|
|
),
|
|
current_merge_base_sha: (.current_tree.merge_base_sha // null),
|
|
current_patch_id: (.current_tree.patch_id // null),
|
|
reviews: (
|
|
[
|
|
.review_pages[]?[]?
|
|
| select((.body // "") | is_gator_body)
|
|
| {
|
|
disposition_id: ("review:" + (.id | tostring)),
|
|
review_id: (.id | tostring),
|
|
kind: "review",
|
|
is_code_review: ((.body // "") | is_code_review_body),
|
|
head_sha: (.commit_id // null),
|
|
base_sha: ((.body // "") | marked_sha("Base SHA")),
|
|
merge_base_sha: ((.body // "") | marked_sha("Merge base SHA")),
|
|
patch_id: ((.body // "") | marked_patch_id),
|
|
payload_version: ((.body // "") | marked_payload_version),
|
|
author: (.user.login // null),
|
|
author_association: (.author_association // null),
|
|
state: (.state // null),
|
|
submitted_at: (.submitted_at // null),
|
|
summary_body: (.body // ""),
|
|
finding_ids: ((.body // "") | explicit_finding_ids)
|
|
}
|
|
]
|
|
| unique_by(.disposition_id)
|
|
| sort_by(.submitted_at)
|
|
),
|
|
issue_comments: (
|
|
[
|
|
.issue_comment_pages[]?[]?
|
|
| select((.body // "") | is_gator_body)
|
|
| {
|
|
disposition_id: ("issue-comment:" + (.id | tostring)),
|
|
comment_id: (.id | tostring),
|
|
kind: "issue_comment",
|
|
is_code_review: ((.body // "") | is_code_review_body),
|
|
head_sha: ((.body // "") | marked_head_sha),
|
|
base_sha: ((.body // "") | marked_sha("Base SHA")),
|
|
merge_base_sha: ((.body // "") | marked_sha("Merge base SHA")),
|
|
patch_id: ((.body // "") | marked_patch_id),
|
|
payload_version: ((.body // "") | marked_payload_version),
|
|
author: (.user.login // null),
|
|
author_association: (.author_association // null),
|
|
submitted_at: (.created_at // null),
|
|
updated_at: (.updated_at // null),
|
|
url: (.html_url // null),
|
|
summary_body: (.body // ""),
|
|
finding_ids: ((.body // "") | explicit_finding_ids)
|
|
}
|
|
]
|
|
| unique_by(.disposition_id)
|
|
| sort_by(.submitted_at)
|
|
),
|
|
threads: (
|
|
[
|
|
.thread_pages[]
|
|
| thread_pull_request.reviewThreads.nodes[]?
|
|
| select((.comments.nodes | length) > 0)
|
|
| select((.comments.nodes[0].body // "") | is_gator_body)
|
|
| {
|
|
thread_id: .id,
|
|
finding_id: (
|
|
((.comments.nodes[0].body // "") | explicit_finding_ids | .[0])
|
|
// ("gator-inline-" + (.comments.nodes[0].databaseId | tostring))
|
|
),
|
|
is_resolved: .isResolved,
|
|
is_outdated: .isOutdated,
|
|
path,
|
|
line,
|
|
resolved_by: (.resolvedBy.login // null),
|
|
origin_review_id: (
|
|
.comments.nodes[0].pullRequestReview.id // null
|
|
),
|
|
comments: [
|
|
.comments.nodes[]
|
|
| {
|
|
id: .databaseId,
|
|
author: (.author.login // null),
|
|
author_association: .authorAssociation,
|
|
body,
|
|
finding_ids: ((.body // "") | explicit_finding_ids),
|
|
created_at: .createdAt,
|
|
updated_at: .updatedAt,
|
|
url,
|
|
commit_oid: (.commit.oid // null),
|
|
review_id: (.pullRequestReview.id // null),
|
|
reply_to: (.replyTo.databaseId // null)
|
|
}
|
|
]
|
|
}
|
|
]
|
|
| unique_by(.thread_id)
|
|
)
|
|
}
|
|
| .dispositions = (
|
|
[.reviews[], .issue_comments[]]
|
|
| sort_by(.submitted_at)
|
|
)
|
|
| .last_reviewed_sha = (
|
|
[
|
|
.dispositions[]
|
|
| select(.is_code_review)
|
|
| .head_sha
|
|
| select(. != null)
|
|
]
|
|
| last // null
|
|
)
|
|
| .last_reviewed_disposition = (
|
|
[.dispositions[] | select(.is_code_review and .head_sha != null)]
|
|
| last // null
|
|
)
|
|
| .last_reviewed_patch_id = (.last_reviewed_disposition.patch_id // null)
|
|
| .review_rounds = (
|
|
[.dispositions[] | select(.is_code_review and .head_sha != null)]
|
|
| unique_by(.head_sha)
|
|
| length
|
|
)
|
|
| .finding_bearing_head_shas = (
|
|
[
|
|
. as $ledger
|
|
| .dispositions[]
|
|
| . as $disposition
|
|
| select(
|
|
.is_code_review and
|
|
.head_sha != null and
|
|
(
|
|
(.finding_ids | length) > 0 or
|
|
(
|
|
.summary_body
|
|
| test(
|
|
"(?i)(blocking findings|\\*\\*(critical|warning))"
|
|
)
|
|
) or
|
|
any(
|
|
$ledger.threads[];
|
|
any(
|
|
.comments[];
|
|
.commit_oid == $disposition.head_sha
|
|
)
|
|
)
|
|
)
|
|
)
|
|
]
|
|
| unique_by(.head_sha)
|
|
| map(.head_sha)
|
|
)
|
|
| .finding_bearing_rounds = (.finding_bearing_head_shas | length)
|
|
| .all_finding_ids = (
|
|
[.dispositions[].finding_ids[], .threads[].finding_id]
|
|
| map(select(. != null))
|
|
)
|
|
| .finding_events = (
|
|
[
|
|
.dispositions[] as $disposition
|
|
| $disposition.finding_ids[]
|
|
| {
|
|
finding_id: .,
|
|
head_sha: $disposition.head_sha,
|
|
submitted_at: $disposition.submitted_at
|
|
}
|
|
]
|
|
)
|
|
| .finding_history = (
|
|
[
|
|
.all_finding_ids[] as $finding_id
|
|
| {
|
|
finding_id: $finding_id,
|
|
first_seen_head_sha: (
|
|
[.finding_events[]
|
|
| select(.finding_id == $finding_id)
|
|
| .head_sha]
|
|
| map(select(. != null))
|
|
| .[0] // null
|
|
),
|
|
review_heads: (
|
|
[.finding_events[]
|
|
| select(.finding_id == $finding_id)
|
|
| .head_sha]
|
|
| map(select(. != null))
|
|
| unique
|
|
)
|
|
}
|
|
]
|
|
| unique_by(.finding_id)
|
|
)
|
|
| .review_telemetry = {
|
|
review_rounds: .review_rounds,
|
|
finding_bearing_rounds: .finding_bearing_rounds,
|
|
unique_findings: (.all_finding_ids | unique | length),
|
|
duplicate_finding_id_occurrences: (
|
|
(.all_finding_ids | length) - (.all_finding_ids | unique | length)
|
|
),
|
|
findings_repeated_across_review_heads: (
|
|
[.finding_history[] | select((.review_heads | length) > 1)]
|
|
| length
|
|
),
|
|
rounds_to_convergence: (
|
|
(.last_reviewed_disposition.head_sha) as $last_head
|
|
| if (
|
|
.review_rounds > 0 and
|
|
(.finding_bearing_head_shas | index($last_head)) == null
|
|
) then .review_rounds
|
|
else null
|
|
end
|
|
),
|
|
review_budget_exhausted: (.finding_bearing_rounds >= 3),
|
|
current_patch_matches_last_review: (
|
|
.current_patch_id != null and
|
|
.last_reviewed_patch_id != null and
|
|
.current_patch_id == .last_reviewed_patch_id
|
|
)
|
|
}
|
|
| .review_scope = {
|
|
mode: (
|
|
if .last_reviewed_sha == null then
|
|
"initial"
|
|
elif (
|
|
.last_reviewed_sha == .current_head_sha or
|
|
.review_telemetry.current_patch_matches_last_review
|
|
) then
|
|
"already_reviewed"
|
|
elif .review_telemetry.review_budget_exhausted then
|
|
"critical_only"
|
|
else
|
|
"follow_up"
|
|
end
|
|
),
|
|
previous_reviewed_sha: .last_reviewed_sha,
|
|
previous_reviewed_patch_id: .last_reviewed_patch_id,
|
|
current_head_sha: .current_head_sha,
|
|
current_base_sha: .current_base_sha,
|
|
current_merge_base_sha: .current_merge_base_sha,
|
|
current_patch_id: .current_patch_id,
|
|
rebase_equivalent: .review_telemetry.current_patch_matches_last_review,
|
|
review_budget_exhausted:
|
|
.review_telemetry.review_budget_exhausted
|
|
}
|
|
| if .pr_author == null then
|
|
error("pull request not found in ledger input")
|
|
elif .current_head_sha == null then
|
|
error("pull request head SHA missing from ledger input")
|
|
elif .current_base_sha == null then
|
|
error("pull request base SHA missing from ledger input")
|
|
else
|
|
.
|
|
end
|
|
'
|