-
Notifications
You must be signed in to change notification settings - Fork 364
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: amend track-pr for new backport label
- Loading branch information
1 parent
227eab8
commit 37b70a7
Showing
1 changed file
with
21 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,8 +41,6 @@ ORG = "determined-ai" | |
CLONED_REMOTE = "origin" | ||
ISSUES_REPO = "release-party-issues-test" if TEST else "release-party-issues" | ||
|
||
CHERRY_PICK_LABEL = "to-cherry-pick" | ||
|
||
NEEDS_TESTING_STATUS = "Needs testing" | ||
FIX_OPEN_STATUS = "Fix (open)" | ||
FIX_CONFLICT_STATUS = "Fix (conflict)" | ||
|
@@ -118,6 +116,14 @@ def add_tracking_issue_to_project(project_id: str, pr_id: str, status: str) -> N | |
add_item_to_project(project_id, issue_id, status) | ||
|
||
|
||
def parse_release_string(release_string): | ||
match = re.search(r"\((\d+\.\d+\.\d+)\)", release_string) | ||
if match: | ||
version = match.group(1) | ||
return f"release-{version}" | ||
return None | ||
|
||
|
||
def find_project(owner: str, query: str, filt: Callable[[dict], bool]) -> dict: | ||
all_projects = gql.search_projects(owner=owner, q=query)["organization"]["projectsV2"]["nodes"] | ||
return next(p for p in all_projects if filt(p)) | ||
|
@@ -139,6 +145,15 @@ def current_project_id() -> str: | |
)["id"] | ||
|
||
|
||
def current_backport_label() -> str: | ||
res = find_project( | ||
ORG, | ||
"Current release", | ||
lambda p: p["title"].startswith("TEST Current release" if TEST else "Current release"), | ||
) | ||
return "backport " + parse_release_string(res["name"]) | ||
|
||
|
||
def project_item_id_for_pr(project_id: str, pr_id: str): | ||
after_cursor = None | ||
while True: | ||
|
@@ -152,111 +167,35 @@ def project_item_id_for_pr(project_id: str, pr_id: str): | |
return None | ||
|
||
|
||
def cherry_pick_skipping_empty(commit): | ||
out = run_capture("git", "cherry-pick", "-x", commit, check=False) | ||
try: | ||
out.check_returncode() | ||
except subprocess.CalledProcessError: | ||
if "The previous cherry-pick is now empty" in out.stderr: | ||
run("git", "cherry-pick", "--skip") | ||
else: | ||
print(out.stdout) | ||
print(out.stderr) | ||
raise | ||
|
||
|
||
def cherry_pick_pr(pr_id: str) -> None: | ||
pr = gql.get_pr_merge_commit_and_url(id=pr_id)["node"] | ||
pr_commit = pr["mergeCommit"]["oid"] | ||
print(f"Cherry-picking {pr_commit}") | ||
|
||
try: | ||
# Find and fetch the PR commit and both release branches. | ||
branch_pat = re.compile(r"/release-(\d+)\.(\d+)\.(\d+)$") | ||
release_branch = max( | ||
( | ||
line.split()[1] | ||
for line in run_capture( | ||
"git", "ls-remote", CLONED_REMOTE, "refs/heads/release-*" | ||
).stdout.splitlines() | ||
), | ||
key=lambda branch: [int(part) for part in branch_pat.search(branch).groups()], | ||
)[len("refs/heads/") :] | ||
print(f"Found release branch {release_branch}") | ||
|
||
run( | ||
"git", | ||
"fetch", | ||
"--depth=2", | ||
CLONED_REMOTE, | ||
pr_commit, | ||
f"{release_branch}:{release_branch}", | ||
) | ||
|
||
# Perform the cherry-pick and push. | ||
run("git", "config", "user.email", "[email protected]") | ||
run("git", "config", "user.name", "Determined CI") | ||
run("git", "checkout", release_branch) | ||
cherry_pick_skipping_empty(pr_commit) | ||
run("git", "push", CLONED_REMOTE, f"{release_branch}:{release_branch}") | ||
|
||
print("Cherry-pick succeeded, updating item status") | ||
set_project_pr_status(current_project_id(), pr_id, FIX_UNRELEASED_STATUS) | ||
except subprocess.CalledProcessError: | ||
import traceback | ||
|
||
traceback.print_exc() | ||
print("Cherry-pick failed, adding PR as conflicted") | ||
set_project_pr_status(current_project_id(), pr_id, FIX_CONFLICT_STATUS) | ||
requests.post( | ||
"https://casper.internal.infra.determined.ai/hubot/conflict", | ||
headers={"X-Casper-Token": CASPER_TOKEN}, | ||
json={"url": pr["url"], "logs_url": os.environ.get("LOGS_URL")}, | ||
) | ||
|
||
|
||
class Actions: | ||
@staticmethod | ||
def pr_merged(pr_id: str): | ||
pr_labels = gql.get_pr_labels(id=pr_id)["node"]["labels"]["nodes"] | ||
print("Labels of merged PR:", [label["name"] for label in pr_labels]) | ||
if any(label["name"] == CHERRY_PICK_LABEL for label in pr_labels): | ||
if any(label["name"] == current_backport_label() for label in pr_labels): | ||
print("Cherry-picking labeled merged PR") | ||
cherry_pick_pr(pr_id) | ||
else: | ||
title = gql.get_pr_title(id=pr_id)["node"]["title"] | ||
if re.match(r"(feat|fix)\S*:", title, re.IGNORECASE) is not None: | ||
print("Adding feat/fix PR") | ||
elif re.match(r"\S+:", title, re.IGNORECASE) is not None: | ||
print("Skipping non-feat/fix PR") | ||
return | ||
else: | ||
print("Adding PR of unknown type") | ||
|
||
print("Adding merged PR to next release project") | ||
add_tracking_issue_to_project(next_project_id(), pr_id, NEEDS_TESTING_STATUS) | ||
|
||
@staticmethod | ||
def pr_labeled(pr_id: str, label: str): | ||
if label != CHERRY_PICK_LABEL: | ||
if label != current_backport_label(): | ||
return | ||
|
||
state = gql.get_pr_state(id=pr_id)["node"]["state"] | ||
if state == "OPEN": | ||
print("Adding labeled open PR to current release project") | ||
add_item_to_project(current_project_id(), pr_id, FIX_OPEN_STATUS) | ||
elif state == "MERGED": | ||
# TODO Maybe delete the tracking issue in the next release that was | ||
# TODO CAROLINA Maybe delete the tracking issue in the next release that was | ||
# created when this merged without a label. | ||
print("Cherry-picking labeled merged PR") | ||
add_item_to_project(current_project_id(), pr_id, FIX_OPEN_STATUS) | ||
cherry_pick_pr(pr_id) | ||
elif state == "CLOSED": | ||
print("Ignoring label addition to closed PR") | ||
|
||
@staticmethod | ||
def pr_unlabeled(pr_id: str, label: str): | ||
if label != CHERRY_PICK_LABEL: | ||
if label != current_backport_label(): | ||
return | ||
|
||
state = gql.get_pr_state(id=pr_id)["node"]["state"] | ||
|
@@ -266,12 +205,6 @@ class Actions: | |
else: | ||
print(f"Ignoring label removal from {state.lower()} PR") | ||
|
||
@staticmethod | ||
def cherry_pick_conflict_resolved(pr_id: str): | ||
# TODO Use Git to confirm the cherry-pick was done. | ||
project_id = current_project_id() | ||
set_project_pr_status(project_id, pr_id, FIX_UNRELEASED_STATUS) | ||
|
||
@staticmethod | ||
def release_unreleased_prs(): | ||
after_cursor = None | ||
|