11from __future__ import annotations
22
3+ import concurrent .futures
34import os .path
45import re
56import tempfile
1011import pre_commit .constants as C
1112from pre_commit import git
1213from pre_commit import output
14+ from pre_commit import xargs
1315from pre_commit .clientlib import InvalidManifestError
1416from pre_commit .clientlib import load_config
1517from pre_commit .clientlib import load_manifest
@@ -71,7 +73,7 @@ def update(self, tags_only: bool, freeze: bool) -> RevInfo:
7173 try :
7274 manifest = load_manifest (os .path .join (tmp , C .MANIFEST_FILE ))
7375 except InvalidManifestError as e :
74- raise RepositoryCannotBeUpdatedError (str ( e ) )
76+ raise RepositoryCannotBeUpdatedError (f'[ { self . repo } ] { e } ' )
7577 else :
7678 hook_ids = frozenset (hook ['id' ] for hook in manifest )
7779
@@ -91,11 +93,24 @@ def _check_hooks_still_exist_at_rev(
9193 hooks_missing = hooks - info .hook_ids
9294 if hooks_missing :
9395 raise RepositoryCannotBeUpdatedError (
94- f'Cannot update because the update target is missing these '
95- f'hooks:\n { ", " .join (sorted (hooks_missing ))} ' ,
96+ f'[ { info . repo } ] Cannot update because the update target is '
97+ f'missing these hooks: { ", " .join (sorted (hooks_missing ))} ' ,
9698 )
9799
98100
101+ def _update_one (
102+ i : int ,
103+ repo : dict [str , Any ],
104+ * ,
105+ tags_only : bool ,
106+ freeze : bool ,
107+ ) -> tuple [int , RevInfo , RevInfo ]:
108+ old = RevInfo .from_config (repo )
109+ new = old .update (tags_only = tags_only , freeze = freeze )
110+ _check_hooks_still_exist_at_rev (repo , new )
111+ return i , old , new
112+
113+
99114REV_LINE_RE = re .compile (r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$' )
100115
101116
@@ -147,45 +162,50 @@ def autoupdate(
147162 tags_only : bool ,
148163 freeze : bool ,
149164 repos : Sequence [str ] = (),
165+ jobs : int = 1 ,
150166) -> int :
151167 """Auto-update the pre-commit config to the latest versions of repos."""
152168 migrate_config (config_file , quiet = True )
153- retv = 0
154- rev_infos : list [RevInfo | None ] = []
155169 changed = False
170+ retv = 0
156171
157- config = load_config ( config_file )
158- for repo_config in config ['repos' ]:
159- if repo_config ['repo' ] in {LOCAL , META }:
160- continue
161-
162- info = RevInfo . from_config ( repo_config )
163- if repos and info . repo not in repos :
164- rev_infos . append ( None )
165- continue
166-
167- output . write ( f'Updating { info . repo } ... ' )
168- try :
169- new_info = info . update ( tags_only = tags_only , freeze = freeze )
170- _check_hooks_still_exist_at_rev ( repo_config , new_info )
171- except RepositoryCannotBeUpdatedError as error :
172- output . write_line ( error . args [ 0 ] )
173- rev_infos . append ( None )
174- retv = 1
175- continue
176-
177- if new_info . rev != info . rev :
178- changed = True
179- if new_info . frozen :
180- updated_to = f' { new_info . frozen } (frozen)'
172+ config_repos = [
173+ repo for repo in load_config ( config_file ) ['repos' ]
174+ if repo ['repo' ] not in {LOCAL , META }
175+ ]
176+
177+ rev_infos : list [ RevInfo | None ] = [ None ] * len ( config_repos )
178+ jobs = jobs or xargs . cpu_count () # 0 => number of cpus
179+ jobs = min ( jobs , len ( repos ) or len ( config_repos )) # max 1-per-thread
180+ jobs = max ( jobs , 1 ) # at least one thread
181+ with concurrent . futures . ThreadPoolExecutor ( jobs ) as exe :
182+ futures = [
183+ exe . submit (
184+ _update_one ,
185+ i , repo , tags_only = tags_only , freeze = freeze ,
186+ )
187+ for i , repo in enumerate ( config_repos )
188+ if not repos or repo [ 'repo' ] in repos
189+ ]
190+ for future in concurrent . futures . as_completed ( futures ):
191+ try :
192+ i , old , new = future . result ()
193+ except RepositoryCannotBeUpdatedError as e :
194+ output . write_line ( str ( e ))
195+ retv = 1
181196 else :
182- updated_to = new_info .rev
183- msg = f'updating { info .rev } -> { updated_to } .'
184- output .write_line (msg )
185- rev_infos .append (new_info )
186- else :
187- output .write_line ('already up to date.' )
188- rev_infos .append (None )
197+ if new .rev != old .rev :
198+ changed = True
199+ if new .frozen :
200+ new_s = f'{ new .frozen } (frozen)'
201+ else :
202+ new_s = new .rev
203+ msg = f'updating { old .rev } -> { new_s } '
204+ rev_infos [i ] = new
205+ else :
206+ msg = 'already up to date!'
207+
208+ output .write_line (f'[{ old .repo } ] { msg } ' )
189209
190210 if changed :
191211 _write_new_config (config_file , rev_infos )
0 commit comments