Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion .github/workflows/flaky_fix_sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
filter: blob:none

- name: Set up Python
uses: actions/setup-python@v4
Expand Down Expand Up @@ -72,7 +73,7 @@ jobs:
- name: Check out repository
uses: actions/checkout@v3
with:
fetch-depth: 0
fetch-depth: 1

- name: Set up Python
uses: actions/setup-python@v4
Expand Down
77 changes: 59 additions & 18 deletions tests/ci/backport_flaky_fixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import argparse
import json
import re
import subprocess
import sys
from datetime import datetime, timezone
Expand All @@ -14,6 +15,35 @@
UPSTREAM_GIT_URL = "https://github.com/{repo}.git"
UPSTREAM_COMMIT_URL = "https://github.com/{repo}/commit/{sha}"

# Branches like antalya-26.3 or stable-25.8 → family label + version label.
_BRANCH_LABEL_RE = re.compile(r'^([a-z]+)-(\d+\.\d+)$')


def labels_for_branch(branch: str) -> list:
labels = ["cicd"]
m = _BRANCH_LABEL_RE.match(branch)
if m:
labels.append(m.group(1)) # e.g. "antalya" or "stable"
labels.append(branch) # e.g. "antalya-26.3" or "stable-25.8"
return labels


def existing_labels(repo: str, labels: list) -> list:
"""Return the subset of labels that already exist in the repo."""
result = subprocess.run(
["gh", "label", "list", "--repo", repo, "--json", "name", "--limit", "1000"],
text=True,
capture_output=True,
)
if result.returncode != 0:
print(f"Warning: could not fetch labels from {repo}, skipping labels", file=sys.stderr)
return []
known = {entry["name"] for entry in json.loads(result.stdout)}
skipped = [l for l in labels if l not in known]
if skipped:
print(f"Skipping non-existent labels: {', '.join(skipped)}", file=sys.stderr)
return [l for l in labels if l in known]


def run_git(*args, check=True, capture=True) -> subprocess.CompletedProcess:
result = subprocess.run(
Expand All @@ -32,13 +62,18 @@ def git_out(*args) -> str:


def fetch_commit(upstream_repo: str, sha: str) -> bool:
"""Fetch a single commit object by SHA from the upstream repo.
"""Fetch a commit and its parent from upstream so cherry-pick can compute the diff.
Returns True on success, False on failure."""
url = UPSTREAM_GIT_URL.format(repo=upstream_repo)
result = run_git("fetch", "--no-tags", "--no-recurse-submodules", url, sha, check=False)
if result.returncode != 0:
print(f"Failed to fetch {sha[:12]} from upstream:\n{result.stderr}", file=sys.stderr)
return False
# cherry-pick needs the parent's tree objects to compute the diff; fetch it too.
parent_result = run_git("rev-parse", "FETCH_HEAD^", check=False)
if parent_result.returncode == 0:
parent_sha = parent_result.stdout.strip()
run_git("fetch", "--no-tags", "--no-recurse-submodules", "--depth=1", url, parent_sha, check=False)
return True


Expand Down Expand Up @@ -91,26 +126,29 @@ def build_pr_body(
return "\n".join(lines)


def create_pr(repo: str, branch: str, base: str, title: str, body: str, dry_run: bool) -> None:
def create_pr(repo: str, branch: str, base: str, title: str, body: str, labels: list, dry_run: bool) -> None:
if dry_run:
print(f"DRY RUN: would open PR in {repo}:", file=sys.stderr)
print(f" title: {title}", file=sys.stderr)
print(f" base: {base}", file=sys.stderr)
print(f" head: {branch}", file=sys.stderr)
print(f" title: {title}", file=sys.stderr)
print(f" base: {base}", file=sys.stderr)
print(f" head: {branch}", file=sys.stderr)
print(f" labels: {', '.join(labels)}", file=sys.stderr)
return

result = subprocess.run(
[
"gh", "pr", "create",
"--repo", repo,
"--base", base,
"--head", branch,
"--title", title,
"--body", body,
],
text=True,
capture_output=False,
)
labels = existing_labels(repo, labels)

cmd = [
"gh", "pr", "create",
"--repo", repo,
"--base", base,
"--head", branch,
"--title", title,
"--body", body,
]
for label in labels:
cmd += ["--label", label]

result = subprocess.run(cmd, text=True, capture_output=False)
if result.returncode != 0:
print("gh pr create failed", file=sys.stderr)
sys.exit(1)
Expand Down Expand Up @@ -196,14 +234,16 @@ def main() -> None:

pr_title = f"Backport flaky-fix commits from upstream ({date_tag})"
pr_body = build_pr_body(upstream_repo, applied, conflicted, fetch_failed)
print(f"\n--- PR title ---\n{pr_title}\n--- PR body ---\n{pr_body}---")
pr_labels = labels_for_branch(base_branch)

if args.dry_run:
print(
f"DRY RUN: would push {backport_branch} and open PR against {base_branch}.",
file=sys.stderr,
)
print(f" Applied: {len(applied)} Conflicted: {len(conflicted)} Fetch failed: {len(fetch_failed)}", file=sys.stderr)
print(f"\n--- PR title ---\n{pr_title}\n--- PR body ---\n{pr_body}---")
create_pr(repo=args.repo, branch=backport_branch, base=base_branch, title=pr_title, body=pr_body, labels=pr_labels, dry_run=True)
run_git("checkout", base_branch)
run_git("branch", "-D", backport_branch)
return
Expand All @@ -216,6 +256,7 @@ def main() -> None:
base=base_branch,
title=pr_title,
body=pr_body,
labels=pr_labels,
dry_run=False,
)

Expand Down
Loading