Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3619f83

Browse files
authored
Merge pull request #610 from pre-commit/config_v2
Enable map configurations (config v2).
2 parents 3f1704f + 8f5675d commit 3619f83

15 files changed

Lines changed: 295 additions & 63 deletions

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
repos:
12
- repo: https://github.com/pre-commit/pre-commit-hooks.git
23
sha: v0.9.1
34
hooks:

pre_commit/clientlib.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import unicode_literals
33

44
import argparse
5+
import collections
56
import functools
67

78
from aspy.yaml import ordered_load
@@ -125,7 +126,11 @@ def validate_manifest_main(argv=None):
125126
ensure_absent=True,
126127
),
127128
)
128-
CONFIG_SCHEMA = schema.Array(CONFIG_REPO_DICT)
129+
CONFIG_SCHEMA = schema.Map(
130+
'Config', None,
131+
132+
schema.RequiredRecurse('repos', schema.Array(CONFIG_REPO_DICT)),
133+
)
129134

130135

131136
def is_local_repo(repo_entry):
@@ -136,10 +141,19 @@ class InvalidConfigError(FatalError):
136141
pass
137142

138143

144+
def ordered_load_normalize_legacy_config(contents):
145+
data = ordered_load(contents)
146+
if isinstance(data, list):
147+
# TODO: Once happy, issue a deprecation warning and instructions
148+
return collections.OrderedDict([('repos', data)])
149+
else:
150+
return data
151+
152+
139153
load_config = functools.partial(
140154
schema.load_from_filename,
141155
schema=CONFIG_SCHEMA,
142-
load_strategy=ordered_load,
156+
load_strategy=ordered_load_normalize_legacy_config,
143157
exc_tp=InvalidConfigError,
144158
)
145159

pre_commit/commands/autoupdate.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pre_commit import output
1212
from pre_commit.clientlib import is_local_repo
1313
from pre_commit.clientlib import load_config
14+
from pre_commit.commands.migrate_config import migrate_config
1415
from pre_commit.repository import Repository
1516
from pre_commit.util import CalledProcessError
1617
from pre_commit.util import cmd_output
@@ -104,21 +105,22 @@ def _write_new_config_file(path, output):
104105
def autoupdate(runner, tags_only):
105106
"""Auto-update the pre-commit config to the latest versions of repos."""
106107
retv = 0
107-
output_configs = []
108+
retv |= migrate_config(runner, quiet=True)
109+
output_repos = []
108110
changed = False
109111

110-
input_configs = load_config(runner.config_file_path)
112+
input_config = load_config(runner.config_file_path)
111113

112-
for repo_config in input_configs:
114+
for repo_config in input_config['repos']:
113115
if is_local_repo(repo_config):
114-
output_configs.append(repo_config)
116+
output_repos.append(repo_config)
115117
continue
116118
output.write('Updating {}...'.format(repo_config['repo']))
117119
try:
118120
new_repo_config = _update_repo(repo_config, runner, tags_only)
119121
except RepositoryCannotBeUpdatedError as error:
120122
output.write_line(error.args[0])
121-
output_configs.append(repo_config)
123+
output_repos.append(repo_config)
122124
retv = 1
123125
continue
124126

@@ -127,12 +129,14 @@ def autoupdate(runner, tags_only):
127129
output.write_line('updating {} -> {}.'.format(
128130
repo_config['sha'], new_repo_config['sha'],
129131
))
130-
output_configs.append(new_repo_config)
132+
output_repos.append(new_repo_config)
131133
else:
132134
output.write_line('already up to date.')
133-
output_configs.append(repo_config)
135+
output_repos.append(repo_config)
134136

135137
if changed:
136-
_write_new_config_file(runner.config_file_path, output_configs)
138+
output_config = input_config.copy()
139+
output_config['repos'] = output_repos
140+
_write_new_config_file(runner.config_file_path, output_config)
137141

138142
return retv
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from __future__ import print_function
2+
from __future__ import unicode_literals
3+
4+
import io
5+
6+
import yaml
7+
from aspy.yaml import ordered_load
8+
9+
10+
def _indent(s):
11+
lines = s.splitlines(True)
12+
return ''.join(' ' * 4 + line if line.strip() else line for line in lines)
13+
14+
15+
def _is_header_line(line):
16+
return (line.startswith(('#', '---')) or not line.strip())
17+
18+
19+
def migrate_config(runner, quiet=False):
20+
retv = 0
21+
22+
with io.open(runner.config_file_path) as f:
23+
contents = f.read()
24+
25+
# Find the first non-header line
26+
lines = contents.splitlines(True)
27+
i = 0
28+
while _is_header_line(lines[i]):
29+
i += 1
30+
31+
header = ''.join(lines[:i])
32+
rest = ''.join(lines[i:])
33+
34+
if isinstance(ordered_load(contents), list):
35+
# If they are using the "default" flow style of yaml, this operation
36+
# will yield a valid configuration
37+
try:
38+
trial_contents = header + 'repos:\n' + rest
39+
yaml.load(trial_contents)
40+
contents = trial_contents
41+
except yaml.YAMLError:
42+
contents = header + 'repos:\n' + _indent(rest)
43+
44+
with io.open(runner.config_file_path, 'w') as f:
45+
f.write(contents)
46+
47+
print('Configuration has been migrated.')
48+
retv = 1
49+
elif not quiet:
50+
print('Configuration is already migrated.')
51+
52+
return retv

pre_commit/commands/sample_config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
SAMPLE_CONFIG = '''\
1111
# See http://pre-commit.com for more information
1212
# See http://pre-commit.com/hooks.html for more hooks
13+
repos:
1314
- repo: https://github.com/pre-commit/pre-commit-hooks
14-
sha: v0.9.1
15+
sha: v0.9.2
1516
hooks:
1617
- id: trailing-whitespace
1718
- id: end-of-file-fixer

pre_commit/main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pre_commit.commands.install_uninstall import install
1515
from pre_commit.commands.install_uninstall import install_hooks
1616
from pre_commit.commands.install_uninstall import uninstall
17+
from pre_commit.commands.migrate_config import migrate_config
1718
from pre_commit.commands.run import run
1819
from pre_commit.commands.sample_config import sample_config
1920
from pre_commit.error_handler import error_handler
@@ -131,6 +132,13 @@ def main(argv=None):
131132
),
132133
)
133134

135+
migrate_config_parser = subparsers.add_parser(
136+
'migrate-config',
137+
help='Migrate list configuration to new map configuration.',
138+
)
139+
_add_color_option(migrate_config_parser)
140+
_add_config_option(migrate_config_parser)
141+
134142
run_parser = subparsers.add_parser('run', help='Run hooks.')
135143
_add_color_option(run_parser)
136144
_add_config_option(run_parser)
@@ -217,6 +225,8 @@ def main(argv=None):
217225
if args.tags_only:
218226
logger.warning('--tags-only is the default')
219227
return autoupdate(runner, tags_only=not args.bleeding_edge)
228+
elif args.command == 'migrate-config':
229+
return migrate_config(runner)
220230
elif args.command == 'run':
221231
return run(runner, args)
222232
elif args.command == 'sample-config':

pre_commit/runner.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ def config_file_path(self):
4040
@cached_property
4141
def repositories(self):
4242
"""Returns a tuple of the configured repositories."""
43-
config = load_config(self.config_file_path)
44-
repositories = tuple(Repository.create(x, self.store) for x in config)
45-
for repository in repositories:
46-
repository.require_installed()
47-
return repositories
43+
repos = load_config(self.config_file_path)['repos']
44+
repos = tuple(Repository.create(x, self.store) for x in repos)
45+
for repo in repos:
46+
repo.require_installed()
47+
return repos
4848

4949
def get_hook_path(self, hook_type):
5050
return os.path.join(self.git_dir, 'hooks', hook_type)

pre_commit/schema.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,13 @@ def check(self, v):
142142
raise ValidationError('Expected a {} map but got a {}'.format(
143143
self.object_name, type(v).__name__,
144144
))
145-
with validate_context('At {}({}={!r})'.format(
145+
if self.id_key is None:
146+
context = 'At {}()'.format(self.object_name)
147+
else:
148+
context = 'At {}({}={!r})'.format(
146149
self.object_name, self.id_key, v.get(self.id_key, MISSING),
147-
)):
150+
)
151+
with validate_context(context):
148152
for item in self.items:
149153
item.check(v)
150154

testing/fixtures.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ def make_config_from_repo(repo_path, sha=None, hooks=None, check=True):
9292
))
9393

9494
if check:
95-
wrapped = validate([config], CONFIG_SCHEMA)
96-
config, = apply_defaults(wrapped, CONFIG_SCHEMA)
95+
wrapped = validate({'repos': [config]}, CONFIG_SCHEMA)
96+
wrapped = apply_defaults(wrapped, CONFIG_SCHEMA)
97+
config, = wrapped['repos']
9798
return config
9899
else:
99100
return config
@@ -106,9 +107,9 @@ def read_config(directory, config_file=C.CONFIG_FILE):
106107

107108

108109
def write_config(directory, config, config_file=C.CONFIG_FILE):
109-
if type(config) is not list:
110+
if type(config) is not list and 'repos' not in config:
110111
assert type(config) is OrderedDict
111-
config = [config]
112+
config = {'repos': [config]}
112113
with io.open(os.path.join(directory, config_file), 'w') as outfile:
113114
outfile.write(ordered_dump(config, **C.YAML_DUMP_KWARGS))
114115

tests/clientlib_test.py

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ def test_validate_config_main(args, expected_output):
6060
('config_obj', 'expected'), (
6161
([], False),
6262
(
63-
[{
63+
{'repos': [{
6464
'repo': '[email protected]:pre-commit/pre-commit-hooks',
6565
'sha': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
6666
'hooks': [{'id': 'pyflakes', 'files': '\\.py$'}],
67-
}],
67+
}]},
6868
True,
6969
),
7070
(
71-
[{
71+
{'repos': [{
7272
'repo': '[email protected]:pre-commit/pre-commit-hooks',
7373
'sha': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
7474
'hooks': [
@@ -78,11 +78,11 @@ def test_validate_config_main(args, expected_output):
7878
'args': ['foo', 'bar', 'baz'],
7979
},
8080
],
81-
}],
81+
}]},
8282
True,
8383
),
8484
(
85-
[{
85+
{'repos': [{
8686
'repo': '[email protected]:pre-commit/pre-commit-hooks',
8787
'sha': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
8888
'hooks': [
@@ -94,7 +94,7 @@ def test_validate_config_main(args, expected_output):
9494
'args': ['foo', 'bar', 'baz'],
9595
},
9696
],
97-
}],
97+
}]},
9898
False,
9999
),
100100
),
@@ -104,29 +104,25 @@ def test_config_valid(config_obj, expected):
104104
assert ret is expected
105105

106106

107-
@pytest.mark.parametrize(
108-
'config_obj', (
109-
[{
110-
'repo': 'local',
111-
'sha': 'foo',
112-
'hooks': [{
113-
'id': 'do_not_commit',
114-
'name': 'Block if "DO NOT COMMIT" is found',
115-
'entry': 'DO NOT COMMIT',
116-
'language': 'pcre',
117-
'files': '^(.*)$',
118-
}],
107+
def test_config_with_local_hooks_definition_fails():
108+
config_obj = {'repos': [{
109+
'repo': 'local',
110+
'sha': 'foo',
111+
'hooks': [{
112+
'id': 'do_not_commit',
113+
'name': 'Block if "DO NOT COMMIT" is found',
114+
'entry': 'DO NOT COMMIT',
115+
'language': 'pcre',
116+
'files': '^(.*)$',
119117
}],
120-
),
121-
)
122-
def test_config_with_local_hooks_definition_fails(config_obj):
118+
}]}
123119
with pytest.raises(schema.ValidationError):
124120
schema.validate(config_obj, CONFIG_SCHEMA)
125121

126122

127123
@pytest.mark.parametrize(
128124
'config_obj', (
129-
[{
125+
{'repos': [{
130126
'repo': 'local',
131127
'hooks': [{
132128
'id': 'arg-per-line',
@@ -136,8 +132,8 @@ def test_config_with_local_hooks_definition_fails(config_obj):
136132
'files': '',
137133
'args': ['hello', 'world'],
138134
}],
139-
}],
140-
[{
135+
}]},
136+
{'repos': [{
141137
'repo': 'local',
142138
'hooks': [{
143139
'id': 'arg-per-line',
@@ -147,7 +143,7 @@ def test_config_with_local_hooks_definition_fails(config_obj):
147143
'files': '',
148144
'args': ['hello', 'world'],
149145
}],
150-
}],
146+
}]},
151147
),
152148
)
153149
def test_config_with_local_hooks_definition_passes(config_obj):

0 commit comments

Comments
 (0)