11#!/usr/bin/env python3
22
3+ import base64
4+ import hashlib
35import json
46import re
57import sys
68import urllib .request
7- from datetime import datetime
9+ from pathlib import Path
810
911
1012def parse_nightly_version (nightly_version ):
@@ -53,7 +55,7 @@ def get_commit_hash_for_nightly(date_str):
5355 Commit hash string
5456 """
5557 api_url = "https://api.github.com/repos/pytorch/pytorch/commits"
56- params = f"?sha=nightly&per_page=100 "
58+ params = f"?sha=nightly&per_page=50 "
5759 url = api_url + params
5860
5961 req = urllib .request .Request (url )
@@ -74,14 +76,21 @@ def get_commit_hash_for_nightly(date_str):
7476 commit_msg = commit .get ("commit" , {}).get ("message" , "" )
7577 # Check if the first line of commit message matches
7678 first_line = commit_msg .split ("\n " )[0 ].strip ()
77- if first_line == target_title or first_line .startswith (f"{ date_str } nightly" ):
78- return commit [ "sha" ]
79+ if first_line .startswith (f"{ date_str } nightly" ):
80+ return extract_hash_from_title ( first_line )
7981
8082 raise ValueError (
8183 f"Could not find commit with title matching '{ target_title } ' in nightly branch"
8284 )
8385
8486
87+ def extract_hash_from_title (title ):
88+ match = re .search (r"\(([0-9a-fA-F]{7,40})\)" , title )
89+ if not match :
90+ raise ValueError (f"Could not extract commit hash from title '{ title } '" )
91+ return match .group (1 )
92+
93+
8594def update_pytorch_pin (commit_hash ):
8695 """
8796 Update .ci/docker/ci_commit_pins/pytorch.txt with the new commit hash.
@@ -95,6 +104,144 @@ def update_pytorch_pin(commit_hash):
95104 print (f"Updated { pin_file } with commit hash: { commit_hash } " )
96105
97106
107+ def should_skip_file (filename ):
108+ """
109+ Check if a file should be skipped during sync (build files).
110+
111+ Args:
112+ filename: Base filename to check
113+
114+ Returns:
115+ True if file should be skipped
116+ """
117+ skip_files = {"BUCK" , "CMakeLists.txt" , "TARGETS" , "targets.bzl" }
118+ return filename in skip_files
119+
120+
121+ def fetch_file_content (commit_hash , file_path ):
122+ """
123+ Fetch file content from GitHub API.
124+
125+ Args:
126+ commit_hash: Commit hash to fetch from
127+ file_path: File path in the repository
128+
129+ Returns:
130+ File content as bytes
131+ """
132+ api_url = f"https://api.github.com/repos/pytorch/pytorch/contents/{ file_path } ?ref={ commit_hash } "
133+
134+ req = urllib .request .Request (api_url )
135+ req .add_header ("Accept" , "application/vnd.github.v3+json" )
136+ req .add_header ("User-Agent" , "ExecuTorch-Bot" )
137+
138+ try :
139+ with urllib .request .urlopen (req ) as response :
140+ data = json .loads (response .read ().decode ())
141+ # Content is base64 encoded
142+ content = base64 .b64decode (data ["content" ])
143+ return content
144+ except urllib .request .HTTPError as e :
145+ print (f"Error fetching file { file_path } : { e } " , file = sys .stderr )
146+ raise
147+
148+
149+ def sync_directory (et_dir , pt_path , commit_hash ):
150+ """
151+ Sync files from PyTorch to ExecuTorch using GitHub API.
152+ Only syncs files that already exist in ExecuTorch - does not add new files.
153+
154+ Args:
155+ et_dir: ExecuTorch directory path
156+ pt_path: PyTorch directory path in the repository (e.g., "c10")
157+ commit_hash: Commit hash to fetch from
158+
159+ Returns:
160+ Number of files grafted
161+ """
162+ files_grafted = 0
163+ print (f"Checking { et_dir } vs pytorch/{ pt_path } ..." )
164+
165+ if not et_dir .exists ():
166+ print (f"Warning: ExecuTorch directory { et_dir } does not exist, skipping" )
167+ return 0
168+
169+ # Loop through files in ExecuTorch directory
170+ for et_file in et_dir .rglob ("*" ):
171+ if not et_file .is_file ():
172+ continue
173+
174+ # Skip build files
175+ if should_skip_file (et_file .name ):
176+ continue
177+
178+ # Construct corresponding path in PyTorch
179+ rel_path = et_file .relative_to (et_dir )
180+ pt_file_path = f"{ pt_path } /{ rel_path } " .replace ("\\ " , "/" )
181+
182+ # Fetch content from PyTorch and compare
183+ try :
184+ pt_content = fetch_file_content (commit_hash , pt_file_path )
185+ et_content = et_file .read_bytes ()
186+
187+ if pt_content != et_content :
188+ print (f"⚠️ Difference detected in { rel_path } " )
189+ print (f"📋 Grafting from PyTorch commit { commit_hash } ..." )
190+
191+ et_file .write_bytes (pt_content )
192+ print (f"✅ Grafted { et_file } " )
193+ files_grafted += 1
194+ except urllib .request .HTTPError as e :
195+ if e .code != 404 : # It's ok to have more files in ET than pytorch/pytorch.
196+ print (f"Error fetching { rel_path } from PyTorch: { e } " )
197+ except Exception as e :
198+ print (f"Error syncing { rel_path } : { e } " )
199+ continue
200+
201+ return files_grafted
202+
203+
204+ def sync_c10_directories (commit_hash ):
205+ """
206+ Sync c10 and torch/headeronly directories from PyTorch to ExecuTorch using GitHub API.
207+
208+ Args:
209+ commit_hash: PyTorch commit hash to sync from
210+
211+ Returns:
212+ Total number of files grafted
213+ """
214+ print ("\n 🔄 Syncing c10 directories from PyTorch via GitHub API..." )
215+
216+ # Get repository root
217+ repo_root = Path .cwd ()
218+
219+ # Define directory pairs to sync (from check_c10_sync.sh)
220+ # Format: (executorch_dir, pytorch_path_in_repo)
221+ dir_pairs = [
222+ (
223+ repo_root / "runtime/core/portable_type/c10/c10" ,
224+ "c10" ,
225+ ),
226+ (
227+ repo_root / "runtime/core/portable_type/c10/torch/headeronly" ,
228+ "torch/headeronly" ,
229+ ),
230+ ]
231+
232+ total_grafted = 0
233+ for et_dir , pt_path in dir_pairs :
234+ files_grafted = sync_directory (et_dir , pt_path , commit_hash )
235+ total_grafted += files_grafted
236+
237+ if total_grafted > 0 :
238+ print (f"\n ✅ Successfully grafted { total_grafted } file(s) from PyTorch" )
239+ else :
240+ print ("\n ✅ No differences found - c10 is in sync" )
241+
242+ return total_grafted
243+
244+
98245def main ():
99246 try :
100247 # Read NIGHTLY_VERSION from torch_pin.py
@@ -112,7 +259,12 @@ def main():
112259 # Update the pin file
113260 update_pytorch_pin (commit_hash )
114261
115- print ("Successfully updated PyTorch commit pin!" )
262+ # Sync c10 directories from PyTorch
263+ sync_c10_directories (commit_hash )
264+
265+ print (
266+ "\n ✅ Successfully updated PyTorch commit pin and synced c10 directories!"
267+ )
116268
117269 except Exception as e :
118270 print (f"Error: { e } " , file = sys .stderr )
0 commit comments