[llvm] [Utils] Fixed rebase in merge-release-pr script (PR #116340)

Tobias Hieta via llvm-commits llvm-commits at lists.llvm.org
Fri Nov 15 00:38:45 PST 2024


https://github.com/tru created https://github.com/llvm/llvm-project/pull/116340

Recently GitHub changed something on their side so we no longer can
rebase release PR's with the API. This means that we now have to
manually rebase the PR locally and then push the results. This fixes
the script that I use to merge PRs to the release branch by changing
the rebase part to do the local rebase and also adds a new option
--rebase-only so that you can rebase the PRs easier.

Minor change is that the script now can take a URL to the pull request
as well as just the PR ID.


>From b3b75db2a3cc97e7b75726cbab652c66adfb8400 Mon Sep 17 00:00:00 2001
From: Tobias Hieta <tobias at hieta.se>
Date: Fri, 15 Nov 2024 09:35:59 +0100
Subject: [PATCH] [Utils] Fixed rebase in merge-release-pr script

Recently GitHub changed something on their side so we no longer can
rebase release PR's with the API. This means that we now have to
manually rebase the PR locally and then push the results. This fixes
the script that I use to merge PRs to the release branch by changing
the rebase part to do the local rebase and also adds a new option
--rebase-only so that you can rebase the PRs easier.

Minor change is that the script now can take a URL to the pull request
as well as just the PR ID.
---
 llvm/utils/release/merge-release-pr.py | 95 +++++++++++++++++++++-----
 1 file changed, 79 insertions(+), 16 deletions(-)

diff --git a/llvm/utils/release/merge-release-pr.py b/llvm/utils/release/merge-release-pr.py
index b9275a2203f49a..ee908e00d06bab 100755
--- a/llvm/utils/release/merge-release-pr.py
+++ b/llvm/utils/release/merge-release-pr.py
@@ -106,27 +106,61 @@ def validate_commits(self, data):
             return False, f"More than 1 commit! {len(data['commits'])}"
         return True
 
-    def validate_pr(self):
+    def _normalize_pr(self, parg: str):
+        if parg.isdigit():
+            return parg
+        elif parg.startswith("https://github.com/llvm/llvm-project/pull"):
+            # try to parse the following url https://github.com/llvm/llvm-project/pull/114089
+            i = parg[parg.rfind("/") + 1 :]
+            if not i.isdigit():
+                raise RuntimeError(f"{i} is not a number, malformatted input.")
+            return i
+        else:
+            raise RuntimeError(
+                f"PR argument must be PR ID or pull request URL - {parg} is wrong."
+            )
+
+    def load_pr_data(self):
+        self.args.pr = self._normalize_pr(self.args.pr)
         fields_to_fetch = [
             "baseRefName",
+            "commits",
+            "headRefName",
+            "headRepository",
+            "headRepositoryOwner",
             "reviewDecision",
-            "title",
+            "state",
             "statusCheckRollup",
+            "title",
             "url",
-            "state",
-            "commits",
         ]
+        print(f"> Loading PR {self.args.pr}...")
         o = self.run_gh(
             "pr",
             ["view", self.args.pr, "--json", ",".join(fields_to_fetch)],
         )
-        prdata = json.loads(o)
+        self.prdata = json.loads(o)
 
         # save the baseRefName (target branch) so that we know where to push
-        self.target_branch = prdata["baseRefName"]
+        self.target_branch = self.prdata["baseRefName"]
+        srepo = self.prdata["headRepository"]["name"]
+        sowner = self.prdata["headRepositoryOwner"]["login"]
+        self.source_url = f"https://github.com/{sowner}/{srepo}"
+        self.source_branch = self.prdata["headRefName"]
+
+        if srepo != "llvm-project":
+            print("The target repo is NOT llvm-project, check the PR!")
+            sys.exit(1)
+
+        if sowner == "llvm":
+            print(
+                "The source owner should never be github.com/llvm, double check the PR!"
+            )
+            sys.exit(1)
 
-        print(f"> Handling PR {self.args.pr} - {prdata['title']}")
-        print(f">   {prdata['url']}")
+    def validate_pr(self):
+        print(f"> Handling PR {self.args.pr} - {self.prdata['title']}")
+        print(f">   {self.prdata['url']}")
 
         VALIDATIONS = {
             "state": self.validate_state,
@@ -141,7 +175,7 @@ def validate_pr(self):
         total_ok = True
         for val_name, val_func in VALIDATIONS.items():
             try:
-                validation_data = val_func(prdata)
+                validation_data = val_func(self.prdata)
             except:
                 validation_data = False
             ok = None
@@ -166,13 +200,20 @@ def validate_pr(self):
         return total_ok
 
     def rebase_pr(self):
-        print("> Rebasing")
-        self.run_gh("pr", ["update-branch", "--rebase", self.args.pr])
-        print("> Waiting for GitHub to update PR")
-        time.sleep(4)
+        print("> Fetching upstream")
+        subprocess.run(["git", "fetch", "--all"], check=True)
+        print("> Rebasing...")
+        subprocess.run(
+            ["git", "rebase", self.args.upstream + "/" + self.target_branch], check=True
+        )
+        print("> Publish rebase...")
+        subprocess.run(
+            ["git", "push", "--force", self.source_url, f"HEAD:{self.source_branch}"]
+        )
 
     def checkout_pr(self):
         print("> Fetching PR changes...")
+        self.merge_branch = "llvm_merger_" + self.args.pr
         self.run_gh(
             "pr",
             [
@@ -180,10 +221,21 @@ def checkout_pr(self):
                 self.args.pr,
                 "--force",
                 "--branch",
-                "llvm_merger_" + self.args.pr,
+                self.merge_branch,
             ],
         )
 
+        # get the branch information so that we can use it for
+        # pushing later.
+        p = subprocess.run(
+            ["git", "config", f"branch.{self.merge_branch}.merge"],
+            check=True,
+            capture_output=True,
+            text=True,
+        )
+        upstream_branch = p.stdout.strip().replace("refs/heads/", "")
+        print(upstream_branch)
+
     def push_upstream(self):
         print("> Pushing changes...")
         subprocess.run(
@@ -201,7 +253,7 @@ def delete_local_branch(self):
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "pr",
-        help="The Pull Request ID that should be merged into a release.",
+        help="The Pull Request ID that should be merged into a release. Can be number or URL",
     )
     parser.add_argument(
         "--skip-validation",
@@ -224,9 +276,20 @@ def delete_local_branch(self):
     parser.add_argument(
         "--validate-only", action="store_true", help="Only run the validations."
     )
+    parser.add_argument(
+        "--rebase-only", action="store_true", help="Only rebase and exit"
+    )
     args = parser.parse_args()
 
     merger = PRMerger(args)
+    merger.load_pr_data()
+
+    if args.rebase_only:
+        merger.checkout_pr()
+        merger.rebase_pr()
+        merger.delete_local_branch()
+        sys.exit(0)
+
     if not merger.validate_pr():
         print()
         print(
@@ -239,8 +302,8 @@ def delete_local_branch(self):
         print("! --validate-only passed, will exit here")
         sys.exit(0)
 
-    merger.rebase_pr()
     merger.checkout_pr()
+    merger.rebase_pr()
 
     if args.no_push:
         print()



More information about the llvm-commits mailing list