Skip to content

Commit ddbee32

Browse files
committed
add --jobs option to autoupdate
1 parent bab5f70 commit ddbee32

File tree

4 files changed

+73
-44
lines changed

4 files changed

+73
-44
lines changed

pre_commit/commands/autoupdate.py

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import concurrent.futures
34
import os.path
45
import re
56
import tempfile
@@ -10,6 +11,7 @@
1011
import pre_commit.constants as C
1112
from pre_commit import git
1213
from pre_commit import output
14+
from pre_commit import xargs
1315
from pre_commit.clientlib import InvalidManifestError
1416
from pre_commit.clientlib import load_config
1517
from 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+
99114
REV_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)

pre_commit/lang_base.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import contextlib
4-
import multiprocessing
54
import os
65
import random
76
import re
@@ -15,9 +14,9 @@
1514

1615
import pre_commit.constants as C
1716
from pre_commit import parse_shebang
17+
from pre_commit import xargs
1818
from pre_commit.prefix import Prefix
1919
from pre_commit.util import cmd_output_b
20-
from pre_commit.xargs import xargs
2120

2221
FIXED_RANDOM_SEED = 1542676187
2322

@@ -140,10 +139,7 @@ def target_concurrency() -> int:
140139
if 'TRAVIS' in os.environ:
141140
return 2
142141
else:
143-
try:
144-
return multiprocessing.cpu_count()
145-
except NotImplementedError:
146-
return 1
142+
return xargs.cpu_count()
147143

148144

149145
def _shuffled(seq: Sequence[str]) -> list[str]:
@@ -171,7 +167,7 @@ def run_xargs(
171167
# ordering.
172168
file_args = _shuffled(file_args)
173169
jobs = target_concurrency()
174-
return xargs(cmd, file_args, target_concurrency=jobs, color=color)
170+
return xargs.xargs(cmd, file_args, target_concurrency=jobs, color=color)
175171

176172

177173
def hook_cmd(entry: str, args: Sequence[str]) -> tuple[str, ...]:

pre_commit/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,13 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser:
226226
help='Store "frozen" hashes in `rev` instead of tag names',
227227
)
228228
autoupdate_parser.add_argument(
229-
'--repo', dest='repos', action='append', metavar='REPO',
229+
'--repo', dest='repos', action='append', metavar='REPO', default=[],
230230
help='Only update this repository -- may be specified multiple times.',
231231
)
232+
autoupdate_parser.add_argument(
233+
'-j', '--jobs', type=int, default=1,
234+
help='Number of threads to use. (default %(default)s).',
235+
)
232236

233237
_add_cmd('clean', help='Clean out pre-commit files.')
234238

@@ -372,6 +376,7 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser:
372376
tags_only=not args.bleeding_edge,
373377
freeze=args.freeze,
374378
repos=args.repos,
379+
jobs=args.jobs,
375380
)
376381
elif args.command == 'clean':
377382
return clean(store)

pre_commit/xargs.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import concurrent.futures
44
import contextlib
55
import math
6+
import multiprocessing
67
import os
78
import subprocess
89
import sys
@@ -22,6 +23,13 @@
2223
TRet = TypeVar('TRet')
2324

2425

26+
def cpu_count() -> int:
27+
try:
28+
return multiprocessing.cpu_count()
29+
except NotImplementedError:
30+
return 1
31+
32+
2533
def _environ_size(_env: MutableMapping[str, str] | None = None) -> int:
2634
environ = _env if _env is not None else getattr(os, 'environb', os.environ)
2735
size = 8 * len(environ) # number of pointers in `envp`

0 commit comments

Comments
 (0)