mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-19 04:03:06 +08:00
Also allow mentioning the PR like now. The numbers of issues must match the issues referenced by the PR.
141 lines
4.3 KiB
Python
Executable File
141 lines
4.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
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):
|
|
patterns = [r"^Fixes:\s*.*$", r"^Implements:\s*.*$", r"^Thanks:\s*.*$"]
|
|
for pattern in patterns:
|
|
if re.match(pattern, line):
|
|
return True
|
|
return False
|
|
|
|
|
|
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) != 2:
|
|
print("Please provide a file name as a command-line argument.")
|
|
sys.exit(1)
|
|
|
|
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__":
|
|
main()
|