|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" |
|
This should help you prepare a patch, automatically extracting the commits to cherry-pick |
|
in chronological order to avoid merge conflicts. An equivalent way to do this is to use |
|
`git log --pretty=oneline HEAD...v4.41.0` and grep. |
|
|
|
Potential TODO: automatically cherry-picks them. |
|
|
|
Pass in a list of PR: |
|
`python utils/patch_helper.py --prs 31108 31054 31008 31010 31004` |
|
will produce the following: |
|
```bash |
|
Skipping invalid version tag: list |
|
Skipping invalid version tag: localattn1 |
|
Git cherry-pick commands to run: |
|
git cherry-pick 03935d300d60110bb86edb49d2315089cfb19789 #2024-05-24 11:00:59+02:00 |
|
git cherry-pick bdb9106f247fca48a71eb384be25dbbd29b065a8 #2024-05-24 19:02:55+02:00 |
|
git cherry-pick 84c4b72ee99e8e65a8a5754a5f9d6265b45cf67e #2024-05-27 10:34:14+02:00 |
|
git cherry-pick 936ab7bae5e040ec58994cb722dd587b9ab26581 #2024-05-28 11:56:05+02:00 |
|
git cherry-pick 0bef4a273825d2cfc52ddfe62ba486ee61cc116f #2024-05-29 13:33:26+01:00 |
|
``` |
|
""" |
|
|
|
import json |
|
import subprocess |
|
|
|
import transformers |
|
|
|
|
|
LABEL = "for patch" |
|
REPO = "huggingface/transformers" |
|
|
|
|
|
def get_release_branch_name(): |
|
"""Derive branch name from transformers version.""" |
|
major, minor, *_ = transformers.__version__.split(".") |
|
major = int(major) |
|
minor = int(minor) |
|
|
|
if minor == 0: |
|
|
|
major -= 1 |
|
|
|
raise ValueError("Minor version is 0; need logic to find previous major version's last minor") |
|
else: |
|
minor -= 1 |
|
|
|
return f"v{major}.{minor}-release" |
|
|
|
|
|
def checkout_branch(branch): |
|
"""Checkout the target branch.""" |
|
try: |
|
subprocess.run(["git", "checkout", branch], check=True) |
|
print(f"β
Checked out branch: {branch}") |
|
except subprocess.CalledProcessError: |
|
print(f"β Failed to checkout branch: {branch}. Does it exist?") |
|
exit(1) |
|
|
|
|
|
def get_prs_by_label(label): |
|
"""Call gh CLI to get PRs with a specific label.""" |
|
cmd = [ |
|
"gh", |
|
"pr", |
|
"list", |
|
"--label", |
|
label, |
|
"--state", |
|
"all", |
|
"--json", |
|
"number,title,mergeCommit,url", |
|
"--limit", |
|
"100", |
|
] |
|
result = subprocess.run(cmd, capture_output=True, text=True) |
|
result.check_returncode() |
|
prs = json.loads(result.stdout) |
|
for pr in prs: |
|
is_merged = pr.get("mergeCommit", {}) |
|
if is_merged: |
|
pr["oid"] = is_merged.get("oid") |
|
return prs |
|
|
|
|
|
def get_commit_timestamp(commit_sha): |
|
"""Get UNIX timestamp of a commit using git.""" |
|
result = subprocess.run(["git", "show", "-s", "--format=%ct", commit_sha], capture_output=True, text=True) |
|
result.check_returncode() |
|
return int(result.stdout.strip()) |
|
|
|
|
|
def cherry_pick_commit(sha): |
|
"""Cherry-pick a given commit SHA.""" |
|
try: |
|
subprocess.run(["git", "cherry-pick", sha], check=True) |
|
print(f"β
Cherry-picked commit {sha}") |
|
except subprocess.CalledProcessError: |
|
print(f"β οΈ Failed to cherry-pick {sha}. Manual intervention required.") |
|
|
|
|
|
def commit_in_history(commit_sha, base_branch="HEAD"): |
|
"""Return True if commit is already part of base_branch history.""" |
|
result = subprocess.run( |
|
["git", "merge-base", "--is-ancestor", commit_sha, base_branch], |
|
stdout=subprocess.DEVNULL, |
|
stderr=subprocess.DEVNULL, |
|
) |
|
return result.returncode == 0 |
|
|
|
|
|
def main(verbose=False): |
|
branch = get_release_branch_name() |
|
checkout_branch(branch) |
|
prs = get_prs_by_label(LABEL) |
|
|
|
for pr in prs: |
|
sha = pr.get("oid") |
|
if sha: |
|
pr["timestamp"] = get_commit_timestamp(sha) |
|
else: |
|
print("\n" + "=" * 80) |
|
print(f"β οΈ WARNING: PR #{pr['number']} ({sha}) is NOT in main!") |
|
print("β οΈ A core maintainer must review this before cherry-picking.") |
|
print("=" * 80 + "\n") |
|
|
|
prs = [pr for pr in prs if pr.get("timestamp") is not None] |
|
prs.sort(key=lambda pr: pr["timestamp"]) |
|
for pr in prs: |
|
sha = pr.get("oid") |
|
if sha: |
|
if commit_in_history(sha): |
|
if verbose: |
|
print(f"π PR #{pr['number']} ({pr['title']}) already in history. Skipping.") |
|
else: |
|
print(f"π PR #{pr['number']} ({pr['title']}) not in history. Cherry-picking...") |
|
cherry_pick_commit(sha) |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|