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

Skip to content

Commit c300a11

Browse files
committed
Merge pull request #46 from pre-commit/autoupdate_command_44
Autoupdate command
2 parents c695ee9 + 2127945 commit c300a11

12 files changed

Lines changed: 334 additions & 28 deletions

.pre-commit-config.yaml

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
21
- repo: [email protected]:pre-commit/pre-commit-hooks
3-
sha: 76739902911688e8d7b13241409f9facc0e534e4
2+
sha: 8aa14d218d0fe5a2c486498269b3e37d3ba5aad2
43
hooks:
5-
- id: pyflakes
6-
files: '\.py$'
7-
- id: debug-statements
8-
files: '\.py$'
9-
- id: trailing-whitespace
10-
files: '\.(py|sh|yaml)$'
11-
- id: name-tests-test
12-
files: 'tests/.+\.py$'
13-
- id: end-of-file-fixer
14-
files: '\.(py|sh|yaml)$'
15-
4+
- id: pyflakes
5+
files: \.py$
6+
- id: debug-statements
7+
files: \.py$
8+
- id: trailing-whitespace
9+
files: \.(py|sh|yaml)$
10+
- id: name-tests-test
11+
files: tests/.+\.py$
12+
- id: end-of-file-fixer
13+
files: \.(py|sh|yaml)$
1614
- repo: [email protected]:pre-commit/pre-commit
17-
sha: 47b7ca44ed1fcaa83464ed00cef72049ae22c33d
15+
sha: c695ee9a9a78ac73439c52e0085bafec8037bc2d
1816
hooks:
19-
- id: validate_manifest
20-
files: '^manifest.yaml$'
21-
- id: validate_config
22-
files: '^\.pre-commit-config.yaml$'
17+
- id: validate_manifest
18+
files: ^manifest.yaml$
19+
- id: validate_config
20+
files: ^\.pre-commit-config.yaml$

pre_commit/clientlib/validate_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def get_validator(
2424
exception_type on failure.
2525
"""
2626

27-
def validate(filename=None):
27+
def validate(filename=None, load_strategy=yaml.load):
2828
filename = filename or os.path.join(git.get_root(), default_filename)
2929

3030
if not os.path.exists(filename):
@@ -33,7 +33,7 @@ def validate(filename=None):
3333
file_contents = open(filename, 'r').read()
3434

3535
try:
36-
obj = yaml.load(file_contents)
36+
obj = load_strategy(file_contents)
3737
except Exception as e:
3838
raise exception_type(
3939
'File {0} is not a valid yaml file'.format(filename), e,

pre_commit/commands.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
import os
55
import pkg_resources
66
import stat
7+
from plumbum import local
8+
9+
import pre_commit.constants as C
10+
from pre_commit.clientlib.validate_config import load_config
11+
from pre_commit.ordereddict import OrderedDict
12+
from pre_commit.repository import Repository
13+
from pre_commit.yaml_extensions import ordered_dump
14+
from pre_commit.yaml_extensions import ordered_load
715

816

917
def install(runner):
@@ -29,3 +37,83 @@ def uninstall(runner):
2937
os.remove(runner.pre_commit_path)
3038
print('pre-commit uninstalled')
3139
return 0
40+
41+
42+
class RepositoryCannotBeUpdatedError(RuntimeError): pass
43+
44+
45+
def _update_repository(repo_config):
46+
"""Updates a repository to the tip of `master`. If the repository cannot
47+
be updated because a hook that is configured does not exist in `master`,
48+
this raises a RepositoryCannotBeUpdatedError
49+
50+
Args:
51+
repo_config - A config for a repository
52+
"""
53+
repo = Repository(repo_config)
54+
55+
with repo.in_checkout():
56+
local['git']['fetch']()
57+
head_sha = local['git']['rev-parse', 'origin/master']().strip()
58+
59+
# Don't bother trying to update if our sha is the same
60+
if head_sha == repo_config['sha']:
61+
return repo_config
62+
63+
# Construct a new config with the head sha
64+
new_config = OrderedDict(repo_config)
65+
new_config['sha'] = head_sha
66+
new_repo = Repository(new_config)
67+
68+
# See if any of our hooks were deleted with the new commits
69+
hooks = set(repo.hooks.keys())
70+
hooks_missing = hooks - (hooks & set(new_repo.manifest.keys()))
71+
if hooks_missing:
72+
raise RepositoryCannotBeUpdatedError(
73+
'Cannot update because the tip of master is missing these hooks:\n'
74+
'{0}'.format(', '.join(sorted(hooks_missing)))
75+
)
76+
77+
return new_config
78+
79+
80+
def autoupdate(runner):
81+
"""Auto-update the pre-commit config to the latest versions of repos."""
82+
retv = 0
83+
output_configs = []
84+
changed = False
85+
86+
input_configs = load_config(
87+
runner.config_file_path,
88+
load_strategy=ordered_load,
89+
)
90+
91+
for repo_config in input_configs:
92+
print('Updating {0}...'.format(repo_config['repo']), end='')
93+
try:
94+
new_repo_config = _update_repository(repo_config)
95+
except RepositoryCannotBeUpdatedError as e:
96+
print(e.args[0])
97+
output_configs.append(repo_config)
98+
retv = 1
99+
continue
100+
101+
if new_repo_config['sha'] != repo_config['sha']:
102+
changed = True
103+
print(
104+
'updating {0} -> {1}.'.format(
105+
repo_config['sha'], new_repo_config['sha'],
106+
)
107+
)
108+
output_configs.append(new_repo_config)
109+
else:
110+
print('already up to date.')
111+
output_configs.append(repo_config)
112+
113+
if changed:
114+
with open(runner.config_file_path, 'w') as config_file:
115+
config_file.write(
116+
ordered_dump(output_configs, **C.YAML_DUMP_KWARGS)
117+
)
118+
119+
return retv

pre_commit/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@
1010
'ruby',
1111
'node',
1212
])
13+
14+
15+
YAML_DUMP_KWARGS = {
16+
'default_flow_style': False,
17+
'indent': 4,
18+
}

pre_commit/run.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,7 @@ def run(argv):
100100

101101
subparsers.add_parser('uninstall', help='Uninstall the pre-commit script.')
102102

103-
execute_hook = subparsers.add_parser(
104-
'execute-hook', help='Run a single hook.'
105-
)
106-
execute_hook.add_argument('hook', help='The hook-id to run.')
107-
execute_hook.add_argument(
108-
'--all-files', '-a', action='store_true', default=False,
109-
help='Run on all the files in the repo.',
110-
)
103+
subparsers.add_parser('autoupdate', help='Auto-update hooks config.')
111104

112105
run = subparsers.add_parser('run', help='Run hooks.')
113106
run.add_argument('hook', nargs='?', help='A single hook-id to run'),
@@ -130,6 +123,8 @@ def run(argv):
130123
return commands.install(runner)
131124
elif args.command == 'uninstall':
132125
return commands.uninstall(runner)
126+
elif args.command == 'autoupdate':
127+
return commands.autoupdate(runner)
133128
elif args.command == 'run':
134129
if args.hook:
135130
return run_single_hook(runner, args.hook, all_files=args.all_files)

pre_commit/yaml_extensions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
import yaml
3+
4+
from pre_commit.ordereddict import OrderedDict
5+
6+
7+
# Adapted from http://stackoverflow.com/a/21912744/812183
8+
9+
def ordered_load(s):
10+
class OrderedLoader(yaml.loader.Loader): pass
11+
def constructor(loader, node):
12+
return OrderedDict(loader.construct_pairs(node))
13+
OrderedLoader.add_constructor(
14+
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
15+
constructor,
16+
)
17+
return yaml.load(s, Loader=OrderedLoader)
18+
19+
20+
def ordered_dump(s, **kwargs):
21+
class OrderedDumper(yaml.dumper.SafeDumper): pass
22+
def dict_representer(dumper, data):
23+
return dumper.represent_mapping(
24+
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
25+
data.items(),
26+
)
27+
OrderedDumper.add_representer(OrderedDict, dict_representer)
28+
return yaml.dump(s, Dumper=OrderedDumper, **kwargs)

testing/auto_namedtuple.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
import collections
3+
4+
def auto_namedtuple(classname='auto_namedtuple', **kwargs):
5+
"""Returns an automatic namedtuple object.
6+
7+
Args:
8+
classname - The class name for the returned object.
9+
**kwargs - Properties to give the returned object.
10+
"""
11+
return (collections.namedtuple(classname, kwargs.keys())(**kwargs))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- id: bar
2+
name: Bar
3+
entry: bar
4+
language: python
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
foo: bar
2+
bar: baz

tests/clientlib/validate_base_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from pre_commit import git
99
from pre_commit.clientlib.validate_base import get_validator
10+
from pre_commit.ordereddict import OrderedDict
11+
from pre_commit.yaml_extensions import ordered_load
1012
from testing.util import get_resource_path
1113

1214

@@ -71,3 +73,11 @@ def test_raises_when_additional_validation_fails(additional_validator):
7173
def test_returns_object_after_validating(noop_validator):
7274
ret = noop_validator(get_resource_path('array_yaml_file.yaml'))
7375
assert ret == ['foo', 'bar']
76+
77+
78+
def test_load_strategy(noop_validator):
79+
ret = noop_validator(
80+
get_resource_path('ordering_data_test.yaml'),
81+
load_strategy=ordered_load,
82+
)
83+
assert type(ret) is OrderedDict

0 commit comments

Comments
 (0)