Allow mentioning fixed issues in the changelog

Also allow mentioning the PR like now. The numbers of issues must match
the issues referenced by the PR.
This commit is contained in:
Alexander Kuzmenkov 2024-01-18 18:11:18 +01:00
parent de93e3916a
commit 2174a7188d
2 changed files with 127 additions and 19 deletions

View File

@ -22,15 +22,26 @@ jobs:
name: Check for file with CHANGELOG entry
runs-on: ubuntu-latest
steps:
- name: Install Linux Dependencies
run: |
sudo apt-get update
sudo apt-get install pip
- name: Install Python Dependencies
run: |
pip install PyGithub
- name: Checkout source
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Check if the pull request adds file in ".unreleased" folder
shell: bash --norc --noprofile {0}
env:
BODY: ${{ github.event.pull_request.body }}
GITHUB_TOKEN: ${{ secrets.ORG_AUTOMATION_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
folder=".unreleased"

View File

@ -4,6 +4,68 @@ import sys
import re
import os
import github # this is PyGithub.
import requests
import string
def run_query(query):
"""A simple function to use requests.post to make the GraphQL API call."""
request = requests.post(
"https://api.github.com/graphql",
json={"query": query},
headers={"Authorization": f'Bearer {os.environ.get("GITHUB_TOKEN")}'},
timeout=20,
)
response = request.json()
# Have to work around the unique GraphQL convention of returning 200 for errors.
if request.status_code != 200 or "errors" in response:
raise ValueError(
f"Query failed to run by returning code of {request.status_code}."
f"\nQuery: '{query}'"
f"\nResponse: '{request.json()}'"
)
return response
def get_referenced_issues(pr_number):
"""Get the numbers of issue fixed by the given pull request."""
ref_result = run_query(
string.Template(
"""
query {
repository(owner: "timescale", name: "timescaledb") {
pullRequest(number: $pr_number) {
closingIssuesReferences(first: 100) {
edges {
node {
number
}
}
}
}
}
}
"""
).substitute({"pr_number": pr_number})
)
# The above returns {'data': {'repository': {'pullRequest': {'closingIssuesReferences': {'edges': [{'node': {'number': 4944}}]}}}}}
ref_edges = ref_result["data"]["repository"]["pullRequest"][
"closingIssuesReferences"
]["edges"]
if not ref_edges:
return []
return [edge["node"]["number"] for edge in ref_edges if edge]
# Check if a line matches any of the specified patterns
def is_valid_line(line):
@ -15,28 +77,63 @@ def is_valid_line(line):
def main():
github_obj = github.Github(os.environ.get("GITHUB_TOKEN"))
repo = github_obj.get_repo("timescale/timescaledb")
# Get the file name from the command line argument
if len(sys.argv) > 1:
file_name = sys.argv[1]
pr_number_seen = False
pr_num_str = f'#{os.environ["PR_NUMBER"]} '
# Read the file and check non-empty lines
with open(file_name, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
pr_number_seen |= pr_num_str in line
if line and not is_valid_line(line):
print(f'Invalid entry in change log: "{line}"')
sys.exit(1)
if not pr_number_seen:
print(
f'Expected that the changelog contains a reference to the PR: "{pr_num_str}"'
)
sys.exit(1)
else:
if len(sys.argv) != 2:
print("Please provide a file name as a command-line argument.")
sys.exit(1)
sys.exit(0)
file_name = sys.argv[1]
this_pr_number = int(os.environ["PR_NUMBER"])
pr_issues = set(get_referenced_issues(this_pr_number))
# Read the file and check non-empty lines
changelog_issues = set()
with open(file_name, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if not is_valid_line(line):
print(f'Invalid entry in change log: "{line}"')
sys.exit(1)
# The referenced issue number should be valid.
for issue_number in re.findall("#([0-9]+)", line):
issue_number = int(issue_number)
try:
issue = repo.get_issue(number=issue_number)
except github.UnknownObjectException:
print(
f"The changelog entry references an invalid issue #{issue_number}:\n{line}"
)
sys.exit(1)
as_pr = None
try:
as_pr = issue.as_pull_request()
except github.UnknownObjectException:
# Not a pull request
pass
# Accept references to PR itself.
if as_pr:
if issue_number != this_pr_number:
print(
f"The changelog for PR #{this_pr_number} references another PR #{issue_number}"
)
sys.exit(1)
changelog_issues = pr_issues
else:
changelog_issues.add(issue_number)
if changelog_issues != pr_issues:
print(
"Instead of "
+ (f"the issues {pr_issues}" if pr_issues else "no issues")
+ f" linked to the PR #{this_pr_number}, the changelog references "
+ (f"the issues {changelog_issues}" if changelog_issues else "no issues")
)
sys.exit(1)
if __name__ == "__main__":