Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 63 additions & 22 deletions .github/workflows/greptile-label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,48 @@ name: Greptile label
# Greptile reviews only pull requests labelled `greptile` (.greptile/config.json).
# Every 30 minutes this job adds the label to each open, non-draft pull
# request with more than 400 additions plus deletions that carries neither
# `greptile` nor `no-greptile`, so a smaller one gets a Greptile review only
# if someone adds the label by hand. Greptile's reference lists a pull
# request being given a trigger label among the openings it reviews, and a
# label added after a pull request opened starts its review then: within
# about half an hour of a pull request opening, passing 400 lines or being
# marked ready, and later when GitHub's scheduler is busy.
# `greptile` nor `no-greptile` and hasn't been updated in the last two
# minutes, so a smaller one gets a Greptile review only if someone adds the
# label by hand. Greptile's reference lists a pull request being given a
# trigger label among the openings it reviews, so its review starts when this
# job's label lands: within about half an hour of a pull request opening,
# passing 400 lines or being marked ready, and later when GitHub's scheduler
# is busy.
#
# Each run lists every open pull request, up to the list call's 500, so the
# next run catches up on one that was missed or delayed. A draft past the
# line is labelled once it's marked ready. The label is only ever added: a
# A run labels a pull request only once two minutes have passed since its
# last update, so one updated less than two minutes before a run waits for a
# later run, usually the next, about half an hour later. On Oct 19, a label
# added seven seconds after opening started no review on either of the two
# pull requests it happened to, while on Oct 18 labels added 35 to 105 seconds
# after opening started a review on each of seven. The two minutes are a
# margin over those cases, since the boundary between seven and 35 seconds
# hasn't been measured. The wait counts from any update, not only from the
# openings Greptile's reference names (opened, reopened, ready for review),
# because a label Greptile drops stays on the pull request, so no later run
# adds it again, while waiting delays the label by a run each time the pull
# request is updated in the two minutes before one. A pull request's update
# time is when it opened or later, and comments, reviews, labels and marking
# it ready move it. Reopening it and pushing to it are expected to move it as
# well, though that hasn't been checked. A pull request whose update time
# doesn't parse gets no wait, and a warning in the run's log names it if it's
# past 400 lines or has no line counts.
#
# Each run lists every open pull request, up to the list call's 500, so a
# later run catches up on one that was missed or delayed. A draft past the
# line is labelled after it's marked ready. The label is only ever added: a
# pull request that later shrinks keeps it, and removing it from one past the
# line doesn't skip the review, since the next run adds it back.
# `no-greptile` is the way to skip a review.
# line doesn't skip the review, since a later run adds it back. `no-greptile`
# is the way to skip a review.
#
# A schedule rather than pull_request_target, which GitHub's default event
# policy for public repositories blocks once it's enforced, from November 2,
# 2026. A scheduled run uses this file as the default branch has it, reads
# pull request numbers, line counts, draft flags and labels through the API,
# and checks nothing out. GitHub disables a public repository's scheduled
# workflows after 60 days without activity (enable this one again from the
# Actions tab), and sends a scheduled workflow's notifications to whoever last
# changed its cron line. workflow_dispatch runs it on demand.
# pull request numbers, line counts, draft flags, labels and update times
# through the API, and checks nothing out. GitHub disables a public
# repository's scheduled workflows after 60 days without activity (enable this
# one again from the Actions tab), and sends a scheduled workflow's
# notifications to whoever last changed its cron line. workflow_dispatch runs
# it on demand.
on:
schedule:
# Away from the start of the hour, which GitHub names as a time of high
Expand All @@ -49,21 +69,36 @@ jobs:
# names compare case-insensitively, as Greptile's own filters do. jq
# reads a missing count as null, and null plus a number as that
# number, so a candidate whose counts aren't both numbers is named and
# fails the step rather than passing unlabelled. A label that fails for
# one pull request doesn't stop the others.
# fails the step rather than passing unlabelled, once two minutes have
# passed since its last update (at once if that time doesn't parse). A
# label that fails for one pull request doesn't stop the others.
run: |
prs=$(gh pr list --repo "$REPO" --state open --limit 500 \
--json number,additions,deletions,isDraft,labels)
--json number,additions,deletions,isDraft,labels,updatedAt)
open=$(jq length <<< "$prs")
echo "Open pull requests: $open."
candidates=$(jq '[.[] | select(.isDraft | not)
| select(any(.labels[]; (.name | ascii_downcase) == "greptile" or (.name | ascii_downcase) == "no-greptile") | not)]' <<< "$prs")
uncounted=$(jq -r '[.[] | select((.additions | type) != "number" or (.deletions | type) != "number")
# A pull request updated in the last two minutes waits for a later
# run, whether or not it has line counts yet. One whose updatedAt
# doesn't parse gets no wait, as before this rule, and the warning
# names it if it's past 400 lines or has no line counts.
now=$(date -u +%s)
untimed=$(jq -r '[.[] | select(((.updatedAt | fromdateiso8601?) // null) == null)
| select((.additions | type) != "number" or (.deletions | type) != "number" or .additions + .deletions > 400)
| "#\(.number)"] | join(" ")' <<< "$candidates")
settled=$(jq --argjson now "$now" '[.[]
| select(((.updatedAt | fromdateiso8601?) // 0) <= $now - 120)]' <<< "$candidates")
young=$(jq -r --argjson now "$now" '[.[]
| select(((.updatedAt | fromdateiso8601?) // 0) > $now - 120)
| select((.additions | type) != "number" or (.deletions | type) != "number" or .additions + .deletions > 400)
| "#\(.number)"] | join(" ")' <<< "$candidates")
uncounted=$(jq -r '[.[] | select((.additions | type) != "number" or (.deletions | type) != "number")
| "#\(.number)"] | join(" ")' <<< "$settled")
todo=$(jq -r '.[]
| select((.additions | type) == "number" and (.deletions | type) == "number")
| select(.additions + .deletions > 400)
| "\(.number) \(.additions + .deletions)"' <<< "$candidates")
| "\(.number) \(.additions + .deletions)"' <<< "$settled")
labelled=0
failed=""
while read -r number changed; do
Expand All @@ -77,11 +112,17 @@ jobs:
fi
done <<< "$todo"
echo "Labelled $labelled."
if [ -n "$young" ]; then
echo "Left for a later run, updated less than two minutes ago: $young."
fi
if [ -n "$untimed" ]; then
echo "::warning::GitHub gave no readable update time for these, so they got no two-minute wait: $untimed."
fi
if [ -n "$uncounted" ]; then
echo "::error::GitHub gave no line counts for these, so they weren't labelled: $uncounted. To have Greptile review one, add the greptile label to it by hand, or add no-greptile to skip its review; either ends this failure for it."
fi
if [ -n "$failed" ]; then
echo "::error::Labelling failed for these:$failed. GitHub's answer for each is above its 'Labelling #N failed' line, and every later run tries again. To have Greptile review one now, add the greptile label to it by hand, creating the label from the repository's Labels page if it isn't there, or add no-greptile to skip its review; either ends this failure for it."
echo "::error::Labelling failed for these:$failed. GitHub's answer for each is above its 'Labelling #N failed' line, and later runs try again. To have Greptile review one now, add the greptile label to it by hand, creating the label from the repository's Labels page if it isn't there, or add no-greptile to skip its review; either ends this failure for it."
fi
if [ -n "$uncounted$failed" ]; then
exit 1
Expand Down
11 changes: 7 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,13 @@ the house default; plain descriptive titles are accepted.
additions plus deletions pass 400 lines, unless it carries `no-greptile`,
and the review starts when the label lands: within about half an hour of
the PR opening, passing 400 lines or being marked ready, and later when
GitHub's scheduler is busy. A smaller PR gets a Greptile review if a
maintainer adds the label. `no-greptile` skips the review. Removing
`greptile` from a PR past 400 lines doesn't skip it, since the next run
adds the label back.
GitHub's scheduler is busy. A PR updated in the two minutes before a run
waits for a later one. A smaller PR gets a Greptile review if a maintainer
adds the label a minute or more after it's opened, reopened or marked
ready. A label added seven seconds after opening has twice started no
review, and Greptile's reference counts reopening and marking ready as
openings too. `no-greptile` skips the review. Removing `greptile` from a PR
past 400 lines doesn't skip it, since a later run adds the label back.
- Respond by pushing fix commits and replying per finding — "Addressed in
`<sha>`" — rather than force-pushing a rewritten branch. A re-review then
flips the verdict.
Expand Down
Loading