Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix backport script when PR references an issue from another repo #6824

Merged
merged 3 commits into from
Apr 12, 2024
Merged
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
50 changes: 33 additions & 17 deletions scripts/backport.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,19 @@ def get_referenced_issue(pr_number):
"""Get the number of issue fixed by the given pull request.
Returns None if no issue is fixed, or more than one issue"""

# We only need the first issue here. We also request only the first 30 labels,
# because GitHub requires some small restriction there that is counted
# towards the GraphQL API usage quota.
ref_result = run_query(
string.Template(
"""
query {
repository(owner: "timescale", name: "timescaledb") {
pullRequest(number: $pr_number) {
closingIssuesReferences(first: 1) {
edges {
node {
number
}
nodes {
number, title,
labels (first: 30) { nodes { name } }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you document why the limitation of 30 was chosen?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some arbitrary number, it is required here by GitHub. It should be not very big because it's counted towards the usage quota of the GraphQL API. I'll add a comment there.

}
}
}
Expand All @@ -61,16 +63,26 @@ def get_referenced_issue(pr_number):
).substitute({"pr_number": pr_number})
)

# The above returns {'data': {'repository': {'pullRequest': {'closingIssuesReferences': {'edges': [{'node': {'number': 4944}}]}}}}}
# The above returns:
# {'data': {'repository': {'pullRequest': {'closingIssuesReferences': {'nodes': [{'number': 6819,
# 'title': '[Bug]: Segfault when `ts_insert_blocker` function is called',
# 'labels': {'nodes': [{'name': 'bug'}]}}]}}}}}
#
# We can have {'nodes': [None]} in case it references an inaccessble repository,
# just ignore it.

ref_edges = ref_result["data"]["repository"]["pullRequest"][
ref_nodes = ref_result["data"]["repository"]["pullRequest"][
"closingIssuesReferences"
]["edges"]
]["nodes"]

if ref_edges and len(ref_edges) == 1:
return ref_edges[0]["node"]["number"]
if not ref_nodes or len(ref_nodes) != 1 or not ref_nodes[0]:
return None, None, None

return None
number = ref_nodes[0]["number"]
title = ref_nodes[0]["title"]
labels = {x["name"] for x in ref_nodes[0]["labels"]["nodes"]}

return number, title, labels


def set_auto_merge(pr_number):
Expand Down Expand Up @@ -242,26 +254,25 @@ def __init__(self, pygithub_pr_, issue_number_):
self.issue_number = issue_number_


def should_backport_by_labels(pygithub_object):
def should_backport_by_labels(number, title, labels):
"""Should we backport the given PR/issue, judging by the labels?
Note that this works in ternary logic:
True means we must,
False means we must not (tags to disable backport take precedence),
and None means weak no (no tags to either request or disable backport)"""
labels = {label.name for label in pygithub_object.labels}
stopper_labels = labels.intersection(
["disable-auto-backport", "auto-backport-not-done"]
)
if stopper_labels:
print(
f"#{pygithub_object.number} '{pygithub_object.title}' is labeled as '{list(stopper_labels)[0]}' which prevents automated backporting."
f"#{number} '{title}' is labeled as '{list(stopper_labels)[0]}' which prevents automated backporting."
)
return False

force_labels = labels.intersection(["bug", "force-auto-backport"])
if force_labels:
print(
f"#{pygithub_object.number} '{pygithub_object.title}' is labeled as '{list(force_labels)[0]}' which requests automated backporting."
f"#{number} '{title}' is labeled as '{list(force_labels)[0]}' which requests automated backporting."
)
return True

Expand Down Expand Up @@ -308,20 +319,25 @@ def should_backport_by_labels(pygithub_object):
# labels to request backport like "bug", and labels to prevent backport
# like "disable-auto-backport", on both issue and the PR. We're going to use
# the ternary False/None/True logic to combine them properly.
issue_number = get_referenced_issue(pull.number)
issue_number, issue_title, issue_labels = get_referenced_issue(pull.number)
if not issue_number:
should_backport_issue_ternary = None
print(
f"{commit_sha[:9]} belongs to the PR #{pull.number} '{pull.title}' that does not close an issue."
)
else:
issue = source_repo.get_issue(number=issue_number)
should_backport_issue_ternary = should_backport_by_labels(issue)
should_backport_issue_ternary = should_backport_by_labels(
issue_number, issue_title, issue_labels
)
print(
f"{commit_sha[:9]} belongs to the PR #{pull.number} '{pull.title}' "
f"that references the issue #{issue.number} '{issue.title}'."
)
should_backport_pr_ternary = should_backport_by_labels(pull)
pull_labels = {label.name for label in pull.labels}
should_backport_pr_ternary = should_backport_by_labels(
pull.number, pull.title, pull_labels
)

# We backport if either the PR or the issue labels request the backport, and
# none of them prevent it. I'm writing it with `is True` because I don't
Expand Down
Loading