@@ -16,9 +16,13 @@ import json
1616import os
1717import re
1818import sys
19+ import traceback
20+ import subprocess
1921
2022
2123from functools import reduce
24+ from multiprocessing import freeze_support
25+
2226
2327sys .path .append (os .path .dirname (__file__ ))
2428
@@ -34,56 +38,53 @@ sys.path.append(os.path.join(SCRIPT_DIR, 'swift_build_support'))
3438from swift_build_support import shell # noqa (E402)
3539
3640
37- def update_single_repository (repo_path , branch , reset_to_remote , should_clean ,
38- cross_repo ):
41+ def update_single_repository (args ):
42+ repo_path , branch , reset_to_remote , should_clean , cross_repo = args
3943 if not os .path .isdir (repo_path ):
4044 return
4145
42- print ("--- Updating '" + repo_path + "' ---" )
43- with shell .pushd (repo_path , dry_run = False , echo = False ):
44- shell .call (["git" , "fetch" , "--recurse-submodules=yes" ], echo = True )
45-
46- if should_clean :
47- shell .call (['git' , 'clean' , '-fdx' ],
48- echo = True )
49- shell .call (['git' , 'submodule' , 'foreach' , '--recursive' , 'git' ,
50- 'clean' , '-fdx' ], echo = True )
51- shell .call (['git' , 'submodule' , 'foreach' , '--recursive' , 'git' ,
52- 'reset' , '--hard' , 'HEAD' ], echo = True )
53- status = shell .call (['git' , 'reset' , '--hard' , 'HEAD' ],
54- echo = True )
55- if status :
56- print ("Please, commit your changes." )
57- print (status )
58- exit (1 )
59-
60- if branch :
61- status = shell .capture (['git' , 'status' , '--porcelain' , '-uno' ],
62- echo = False )
63- if status :
64- print ("Please, commit your changes." )
65- print (status )
66- exit (1 )
67- shell .call (['git' , 'checkout' , branch ], echo = True )
68-
69- # If we were asked to reset to the specified branch, do the hard
70- # reset and return.
71- if reset_to_remote and not cross_repo :
72- shell .call (['git' , 'reset' , '--hard' , "origin/%s" % branch ],
73- echo = True )
74- return
75-
76- # Prior to Git 2.6, this is the way to do a "git pull
77- # --rebase" that respects rebase.autostash. See
78- # http://stackoverflow.com/a/30209750/125349
79- if not cross_repo :
80- shell .call (["git" , "rebase" , "FETCH_HEAD" ], echo = True )
81- shell .call (["git" , "submodule" , "update" , "--recursive" ],
82- echo = True )
46+ try :
47+ print ("--- Updating '" + repo_path + "' ---" )
48+ with shell .pushd (repo_path , dry_run = False , echo = False ):
49+ shell .run (["git" , "fetch" , "--recurse-submodules=yes" ], echo = True )
50+
51+ if should_clean :
52+ shell .run (['git' , 'clean' , '-fdx' ], echo = True )
53+ shell .run (['git' , 'submodule' , 'foreach' , '--recursive' , 'git' ,
54+ 'clean' , '-fdx' ], echo = True )
55+ shell .run (['git' , 'submodule' , 'foreach' , '--recursive' , 'git' ,
56+ 'reset' , '--hard' , 'HEAD' ], echo = True )
57+ shell .run (['git' , 'reset' , '--hard' , 'HEAD' ],
58+ echo = True , msg = "Please, commit your changes." )
59+
60+ if branch :
61+ shell .run (['git' , 'status' , '--porcelain' , '-uno' ],
62+ echo = False , msg = "Please, commit your changes." )
63+ shell .run (['git' , 'checkout' , branch ], echo = True )
64+
65+ # If we were asked to reset to the specified branch, do the hard
66+ # reset and return.
67+ if reset_to_remote and not cross_repo :
68+ shell .run (['git' , 'reset' , '--hard' , "origin/%s" % branch ],
69+ echo = True )
70+ return
71+
72+ # Prior to Git 2.6, this is the way to do a "git pull
73+ # --rebase" that respects rebase.autostash. See
74+ # http://stackoverflow.com/a/30209750/125349
75+ if not cross_repo :
76+ shell .run (["git" , "rebase" , "FETCH_HEAD" ], echo = True ,
77+ msg = "Cannot rebase: You have unstaged changes. Please commit or stash them." )
78+ shell .run (["git" , "submodule" , "update" , "--recursive" ], echo = True )
79+ except :
80+ (type , value , tb ) = sys .exc_info ()
81+ print ('Error on repo "%s": %s' % (repo_path , traceback .format_exc ()))
82+ return value
8383
8484
8585def update_all_repositories (args , config , scheme_name , cross_repos_pr ):
8686 repo_branch = scheme_name
87+ pool_args = []
8788 for repo_name in config ['repos' ].keys ():
8889 cross_repo = False
8990 if repo_name in args .skip_repository_list :
@@ -105,70 +106,78 @@ def update_all_repositories(args, config, scheme_name, cross_repos_pr):
105106 pr_id = cross_repos_pr [remote_repo_id ]
106107 repo_branch = "ci_pr_{0}" .format (pr_id )
107108 with shell .pushd (repo_path , dry_run = False , echo = False ):
108- shell .call (["git" , "checkout" , v ['repos' ][repo_name ]],
109+ shell .run (["git" , "checkout" , v ['repos' ][repo_name ]],
109110 echo = True )
110- shell .capture (["git" , "branch" , "-D" , repo_branch ],
111+ shell .run (["git" , "branch" , "-D" , repo_branch ],
111112 echo = True , allow_non_zero_exit = True )
112- shell .call (["git" , "fetch" , "origin" ,
113+ shell .run (["git" , "fetch" , "origin" ,
113114 "pull/{0}/merge:{1}"
114115 .format (pr_id , repo_branch )], echo = True )
115116 break
116- update_single_repository (repo_path ,
117- repo_branch ,
118- args .reset_to_remote ,
119- args .clean ,
120- cross_repo )
117+ pool_args .append ([repo_path , repo_branch , args .reset_to_remote , args .clean , cross_repo ])
118+
119+ return shell .run_parallel (update_single_repository , pool_args , args .n_processes )
121120
121+ def obtain_additional_swift_sources (pool_args ):
122+ args , repo_name , repo_info , repo_branch , remote , with_ssh , scheme_name , skip_history , skip_repository_list = pool_args
122123
123- def obtain_additional_swift_sources (
124- config , with_ssh , scheme_name , skip_history , skip_repository_list ):
125124 with shell .pushd (SWIFT_SOURCE_ROOT , dry_run = False ,
126125 echo = False ):
127- for repo_name , repo_info in config ['repos' ].items ():
128- if repo_name in skip_repository_list :
129- print ("--- Skipping '" + repo_name + "' ---" )
130- continue
126+ print ("--- Cloning '" + repo_name + "' ---" )
127+
128+ if skip_history :
129+ shell .run (['git' , 'clone' , '--recursive' , '--depth' , '1' ,
130+ remote , repo_name ], echo = True )
131+ else :
132+ shell .run (['git' , 'clone' , '--recursive' , remote ,
133+ repo_name ], echo = True )
134+ if scheme_name :
135+ src_path = os .path .join (SWIFT_SOURCE_ROOT , repo_name , ".git" )
136+ shell .run (['git' , '--git-dir' , src_path , '--work-tree' ,
137+ os .path .join (SWIFT_SOURCE_ROOT , repo_name ),
138+ 'checkout' , repo_branch ], echo = False )
139+ with shell .pushd (os .path .join (SWIFT_SOURCE_ROOT , repo_name ),
140+ dry_run = False , echo = False ):
141+ shell .run (["git" , "submodule" , "update" , "--recursive" ],
142+ echo = False )
131143
132- if os . path . isdir ( os . path . join ( repo_name , ".git" )):
133- continue
144+ def obtain_all_additional_swift_sources (
145+ args , config , with_ssh , scheme_name , skip_history , skip_repository_list ):
134146
135- print ("--- Cloning '" + repo_name + "' ---" )
147+ pool_args = []
148+ for repo_name , repo_info in config ['repos' ].items ():
149+ if repo_name in skip_repository_list :
150+ print ("--- Skipping '" + repo_name + "' ---" )
151+ continue
152+
153+ if os .path .isdir (os .path .join (repo_name , ".git" )):
154+ continue
136155
137- # If we have a url override, use that url instead of
138- # interpolating.
139- remote_repo_info = repo_info ['remote' ]
140- if 'url' in remote_repo_info :
141- remote = remote_repo_info ['url' ]
156+ # If we have a url override, use that url instead of
157+ # interpolating.
158+ remote_repo_info = repo_info ['remote' ]
159+ if 'url' in remote_repo_info :
160+ remote = remote_repo_info ['url' ]
161+ else :
162+ remote_repo_id = remote_repo_info ['id' ]
163+ if with_ssh is True or 'https-clone-pattern' not in config :
164+ remote = config ['ssh-clone-pattern' ] % remote_repo_id
142165 else :
143- remote_repo_id = remote_repo_info [ 'id' ]
144- if with_ssh is True or 'https-clone-pattern' not in config :
145- remote = config [ 'ssh-clone-pattern' ] % remote_repo_id
146- else :
147- remote = config ['https-clone-pattern' ] % remote_repo_id
148-
149- if skip_history :
150- shell . call ([ 'git' , 'clone' , '--recursive' , '--depth' , '1' ,
151- remote , repo_name ], echo = True )
166+ remote = config [ 'https-clone-pattern' ] % remote_repo_id
167+
168+ repo_branch = None
169+ if scheme_name :
170+ for v in config ['branch-schemes' ]. values ():
171+ if scheme_name not in v [ 'aliases' ]:
172+ continue
173+ repo_branch = v [ 'repos' ][ repo_name ]
174+ break
152175 else :
153- shell .call (['git' , 'clone' , '--recursive' , remote ,
154- repo_name ], echo = True )
155- if scheme_name :
156- for v in config ['branch-schemes' ].values ():
157- if scheme_name not in v ['aliases' ]:
158- continue
159- repo_branch = v ['repos' ][repo_name ]
160- break
161- else :
162- repo_branch = scheme_name
163- src_path = os .path .join (SWIFT_SOURCE_ROOT , repo_name ,
164- ".git" )
165- shell .call (['git' , '--git-dir' , src_path , '--work-tree' ,
166- os .path .join (SWIFT_SOURCE_ROOT , repo_name ),
167- 'checkout' , repo_branch ], echo = False )
168- with shell .pushd (os .path .join (SWIFT_SOURCE_ROOT , repo_name ),
169- dry_run = False , echo = False ):
170- shell .call (["git" , "submodule" , "update" , "--recursive" ],
171- echo = False )
176+ repo_branch = scheme_name
177+
178+ pool_args .append ([args , repo_name , repo_info , repo_branch , remote , with_ssh , scheme_name , skip_history , skip_repository_list ])
179+
180+ return shell .run_parallel (obtain_additional_swift_sources , pool_args , args .n_processes )
172181
173182
174183def validate_config (config ):
@@ -192,7 +201,6 @@ def validate_config(config):
192201 raise RuntimeError ('Configuration file has schemes with duplicate '
193202 'aliases?!' )
194203
195-
196204def main ():
197205 parser = argparse .ArgumentParser (
198206 formatter_class = argparse .RawDescriptionHelpFormatter ,
@@ -242,6 +250,12 @@ By default, updates your checkouts of Swift, SourceKit, LLDB, and SwiftPM.""")
242250 free-form GitHub-style comment.""" ,
243251 metavar = 'GITHUB-COMMENT' ,
244252 dest = 'github_comment' )
253+ parser .add_argument (
254+ "-j" , "--jobs" ,
255+ type = int ,
256+ help = "Number of threads to run at once" ,
257+ default = 0 ,
258+ dest = "n_processes" )
245259 args = parser .parse_args ()
246260
247261 clone = args .clone
@@ -262,19 +276,25 @@ By default, updates your checkouts of Swift, SourceKit, LLDB, and SwiftPM.""")
262276 repos_with_pr = [pr .replace ('/pull/' , '#' ) for pr in repos_with_pr ]
263277 cross_repos_pr = dict (pr .split ('#' ) for pr in repos_with_pr )
264278
279+ clone_results = None
265280 if clone or clone_with_ssh :
266281 # If branch is None, default to using the default branch alias
267282 # specified by our configuration file.
268283 if scheme is None :
269284 scheme = config ['default-branch-scheme' ]
270285
271- obtain_additional_swift_sources (
272- config , clone_with_ssh , scheme , skip_history ,
273- args .skip_repository_list )
274-
275- update_all_repositories (args , config , scheme , cross_repos_pr )
286+ clone_results = obtain_all_additional_swift_sources (args , config ,
287+ clone_with_ssh , scheme , skip_history , args .skip_repository_list )
276288
277- return 0
289+ update_results = update_all_repositories (args , config , scheme , cross_repos_pr )
290+ return (clone_results , update_results )
278291
279292if __name__ == "__main__" :
280- sys .exit (main ())
293+ freeze_support ()
294+ clone_results , update_results = main ()
295+ fail_count = 0
296+ fail_count += shell .check_parallel_results (clone_results , "CLONE" )
297+ fail_count += shell .check_parallel_results (update_results , "UPDATE" )
298+ if fail_count > 0 :
299+ print ("update-checkout failed, fix errors and try again" )
300+ sys .exit (fail_count )
0 commit comments