Skip to content

Commit abd198e

Browse files
committed
Scan and un-verify outdated Gerrit changes
This workflow will query Gerrit and look for changes which were not updated for a while. If there was no activity for: * 2 weeks - the script will suggest user to rebase their change * 4 weeks - the script will set Verified -1 vote on change, and ask user to rebase. Workflow runs daily as 12AM UTC, or can be run on-demand by priviledged users. There's also an additional 12 weeks cut-off date. Changes older than that will NOT be commented on to avoid polluting Gerrit dashboards with very old changes (e.g. 2-3 years old). Signed-off-by: Karol Latecki <[email protected]>
1 parent 0d5c80a commit abd198e

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

.github/scripts/outdated_changes.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import logging
5+
import datetime
6+
from pygerrit2 import GerritRestAPI, HTTPBasicAuth
7+
8+
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
9+
GERRIT_USERNAME = os.getenv("GERRIT_USERNAME")
10+
GERRIT_PASSWORD = os.getenv("GERRIT_PASSWORD")
11+
GERRIT_BASE_URL = os.getenv("GERRIT_BASE_URL", "https://review.spdk.io")
12+
13+
def get_open_changes(gerrit):
14+
query = "".join([
15+
"/changes/",
16+
"?q=project:spdk/spdk status:open -is:private",
17+
"&o=CURRENT_REVISION"
18+
])
19+
logging.info(f"Querying Gerrit with: {query}")
20+
return gerrit.get(query)
21+
22+
def process_changes(gerrit, changes):
23+
now = datetime.datetime.now(datetime.timezone.utc)
24+
two_weeks = datetime.timedelta(weeks=2)
25+
four_weeks = datetime.timedelta(weeks=4)
26+
twelve_weeks = datetime.timedelta(weeks=12)
27+
28+
for change in changes:
29+
change_id = change.get("_number")
30+
project = change.get("project")
31+
subject = change.get("subject", "N/A")
32+
owner = change.get("owner", {}).get("name", "Unknown")
33+
url = os.path.join(GERRIT_BASE_URL, "c", project, '+', str(change_id))
34+
revisions = change.get("revisions", {})
35+
current_revision = next(iter(revisions.values()), {})
36+
created_str = current_revision.get("created")
37+
38+
if not created_str:
39+
logging.warning(f"Change {change_id} has no 'created' field in the current revision.")
40+
continue
41+
42+
created = datetime.datetime.strptime(created_str, "%Y-%m-%d %H:%M:%S.%f000").replace(tzinfo=datetime.timezone.utc)
43+
time_since_update = now - created
44+
if time_since_update > twelve_weeks:
45+
# Change is older than twelve weeks; we don't want VERY old changes to flood Gerrit dashboard,
46+
# so skip them.
47+
continue
48+
49+
logging.info(f"Processing change {url} - {subject} by {owner}")
50+
logging.info(f"Time since last update: {time_since_update.days} days")
51+
message = "OUTDATED PATCH WARNING: Your change has not been updated for at least"
52+
message += f" {time_since_update.days // 7} weeks ({time_since_update.days} days)."
53+
if time_since_update > four_weeks:
54+
message += " This makes it severely outdated. Please rebase your change."
55+
send_comment(gerrit, change_id, message, -1)
56+
elif time_since_update > two_weeks:
57+
message += " Please consider rebasing, make sure you're working with latest code base."
58+
send_comment(gerrit, change_id, message, 0)
59+
60+
def send_comment(gerrit, change_id, message, vote):
61+
logging.info(f"Sending comment to change {change_id}: {message} (Verified={vote})")
62+
try:
63+
# Commented out on a purpose for now. Will uncomment after postitive
64+
# code review, before merging.
65+
# gerrit.post(f"/changes/{change_id}/revisions/current/review", json={
66+
# "message": message,
67+
# "labels": {"Verified": vote}
68+
# })
69+
logging.info(f"Comment sent successfully to change {change_id}.")
70+
except Exception as e:
71+
logging.error(f"Failed to send comment to change {change_id}: {e}")
72+
73+
def main():
74+
logging.basicConfig(
75+
level=getattr(logging, LOG_LEVEL, logging.INFO),
76+
format="%(asctime)s - %(levelname)s - %(message)s"
77+
)
78+
79+
auth = HTTPBasicAuth(GERRIT_USERNAME, GERRIT_PASSWORD)
80+
gerrit = GerritRestAPI(url=GERRIT_BASE_URL, auth=auth)
81+
82+
try:
83+
changes = get_open_changes(gerrit)
84+
process_changes(gerrit, changes)
85+
except Exception as e:
86+
logging.error(f"An error occurred: {e}")
87+
exit(1)
88+
89+
if __name__ == "__main__":
90+
main()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
name: Gerrit outdated changes scan
3+
4+
on:
5+
workflow_dispatch:
6+
schedule:
7+
- cron: "0 0 * * *" # Every 24 hours at midnight UTC
8+
9+
jobs:
10+
outdated-changes-scan:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install pygerrit2
25+
26+
- name: Run outdated changes script
27+
run: python .github/scripts/outdated_changes.py
28+
env:
29+
GERRIT_USERNAME: ${{ secrets.GERRIT_BOT_USER }}
30+
GERRIT_PASSWORD: ${{ secrets.GERRIT_BOT_HTTP_PASSWD }}

0 commit comments

Comments
 (0)