From 5fd30ac0c208730b59f7a4e770d9f3712e59530c Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 3 Nov 2026 18:09:26 +0000 Subject: [PATCH] perf(scripts): fetch PR commits by SHA in github-merge.py Startup took 5-15 s before the first prompt, almost all of it in `git fetch +refs/pull/N/*:... +refs/heads/:...`. GitHub needs about 2 s per git round trip, and that fetch makes three: the capability advertisement, an ls-refs that also lists every tag for tag following, and the pack request. The REST API already reports the PR head SHA, and one more cheap call gives the base-branch tip. Fetching those exact SHAs with --no-tags drops the ls-refs round trip. When the commits are already local, git does not contact the remote at all. GitHub's test merge is only used to compare against after githubmerge.testcmd, so it is fetched only then. If the API lookup or the by-SHA fetch fails, the script falls back to the previous by-name fetch and its error message. Time to the first prompt with the commits already local went from 10.7-14.8 s to 1.2 s; outcomes for clean, conflicting, testcmd and missing-branch runs match the previous script. Co-Authored-By: Claude Opus 5.5 --- contrib/devtools/github-merge.py | 43 +++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/contrib/devtools/github-merge.py b/contrib/devtools/github-merge.py index cfef618bebae..7ec16a2ab2e7 100755 --- a/contrib/devtools/github-merge.py +++ b/contrib/devtools/github-merge.py @@ -9,7 +9,7 @@ # The following temporary branches are created/overwritten and deleted: # * pull/$PULL/base (the current master we're merging onto) # * pull/$PULL/head (the current state of the remote pull request) -# * pull/$PULL/merge (github's merge) +# * pull/$PULL/merge (github's merge, fetched only when githubmerge.testcmd is set) # * pull/$PULL/local-merge (our merge) # In case of a clean merge that is accepted by the user, the local branch with @@ -24,6 +24,7 @@ import json import codecs import unicodedata +from urllib.parse import quote from urllib.request import Request, urlopen from urllib.error import HTTPError @@ -145,6 +146,14 @@ def retrieve_pr_info(repo,pull,ghtoken): req_url = "https://api.github.com/repos/"+repo+"/pulls/"+pull return retrieve_json(req_url,ghtoken) +def retrieve_branch_sha(repo,branch,ghtoken): + req_url = "https://api.github.com/repos/"+repo+"/git/ref/heads/"+quote(branch) + try: + ref = json.load(codecs.getreader('utf-8')(get_response(req_url,ghtoken)))['object'] + return ref['sha'] if ref['type'] == 'commit' else None + except Exception: # any failure falls back to fetching the branch by name + return None + def retrieve_pr_comments(repo,pull,ghtoken): req_url = "https://api.github.com/repos/"+repo+"/issues/"+pull+"/comments" return retrieve_json(req_url,ghtoken,use_pagination=True) @@ -364,12 +373,21 @@ def main(): except subprocess.CalledProcessError: print(f"ERROR: Cannot check out branch {branch}.", file=stderr) sys.exit(3) - try: - subprocess.check_call([GIT,'fetch','-q',host_repo_from,'+refs/pull/'+pull+'/*:refs/heads/pull/'+pull+'/*', - '+refs/heads/'+branch+':refs/heads/'+base_branch]) - except subprocess.CalledProcessError: - print(f"ERROR: Cannot find pull request {pull_reference} or branch {branch} on {host_repo_from}.", file=stderr) - sys.exit(3) + # Fetching the exact commits named by the API skips listing the remote's + # refs, and skips the network entirely when the commits are already local. + # GitHub's test merge is only needed to compare against after testcmd. + pull_shas = {head_branch: info['head']['sha'], base_branch: retrieve_branch_sha(repo_from,branch,ghtoken)} + if testcmd: + pull_shas[merge_branch] = info.get('merge_commit_sha') + fetched = all(pull_shas.values()) and subprocess.call([GIT,'fetch','-q','--no-tags',host_repo_from] + + [f'+{sha}:refs/heads/{ref}' for ref,sha in pull_shas.items()], stderr=devnull) == 0 + if not fetched: + try: + subprocess.check_call([GIT,'fetch','-q',host_repo_from,'+refs/pull/'+pull+'/*:refs/heads/pull/'+pull+'/*', + '+refs/heads/'+branch+':refs/heads/'+base_branch]) + except subprocess.CalledProcessError: + print(f"ERROR: Cannot find pull request {pull_reference} or branch {branch} on {host_repo_from}.", file=stderr) + sys.exit(3) # Ask the user if they want to rebase the branch rebase_reply = ask_prompt("Would you like to rebase the branch? Type 'yes' to rebase or anything else to continue without rebasing.").lower() @@ -427,11 +445,12 @@ def main(): except subprocess.CalledProcessError: print(f"ERROR: Cannot find head of pull request {pull_reference} on {host_repo_from}.", file=stderr) sys.exit(3) - try: - subprocess.check_call([GIT,'--no-pager','log','-q','-1','refs/heads/'+merge_branch], stdout=devnull, stderr=stdout) - except subprocess.CalledProcessError: - print(f"ERROR: Cannot find merge of pull request {pull_reference} on {host_repo_from}.", file=stderr) - sys.exit(3) + if testcmd: + try: + subprocess.check_call([GIT,'--no-pager','log','-q','-1','refs/heads/'+merge_branch], stdout=devnull, stderr=stdout) + except subprocess.CalledProcessError: + print(f"ERROR: Cannot find merge of pull request {pull_reference} on {host_repo_from}.", file=stderr) + sys.exit(3) subprocess.check_call([GIT,'checkout','-q',base_branch]) subprocess.call([GIT,'branch','-q','-D',local_merge_branch], stderr=devnull) subprocess.check_call([GIT,'checkout','-q','-b',local_merge_branch])