|
| 1 | +from __future__ import print_function |
| 2 | +from __future__ import unicode_literals |
| 3 | + |
| 4 | +import sys |
| 5 | + |
| 6 | +from asottile.ordereddict import OrderedDict |
| 7 | +from asottile.yaml import ordered_dump |
| 8 | +from asottile.yaml import ordered_load |
| 9 | +from plumbum import local |
| 10 | + |
| 11 | +import pre_commit.constants as C |
| 12 | +from pre_commit.clientlib.validate_config import CONFIG_JSON_SCHEMA |
| 13 | +from pre_commit.clientlib.validate_config import load_config |
| 14 | +from pre_commit.jsonschema_extensions import remove_defaults |
| 15 | +from pre_commit.repository import Repository |
| 16 | + |
| 17 | + |
| 18 | +class RepositoryCannotBeUpdatedError(RuntimeError): |
| 19 | + pass |
| 20 | + |
| 21 | + |
| 22 | +def _update_repository(repo_config, runner): |
| 23 | + """Updates a repository to the tip of `master`. If the repository cannot |
| 24 | + be updated because a hook that is configured does not exist in `master`, |
| 25 | + this raises a RepositoryCannotBeUpdatedError |
| 26 | +
|
| 27 | + Args: |
| 28 | + repo_config - A config for a repository |
| 29 | + """ |
| 30 | + repo = Repository.create(repo_config, runner.store) |
| 31 | + |
| 32 | + with local.cwd(repo.repo_path_getter.repo_path): |
| 33 | + local['git']['fetch']() |
| 34 | + head_sha = local['git']['rev-parse', 'origin/master']().strip() |
| 35 | + |
| 36 | + # Don't bother trying to update if our sha is the same |
| 37 | + if head_sha == repo_config['sha']: |
| 38 | + return repo_config |
| 39 | + |
| 40 | + # Construct a new config with the head sha |
| 41 | + new_config = OrderedDict(repo_config) |
| 42 | + new_config['sha'] = head_sha |
| 43 | + new_repo = Repository.create(new_config, runner.store) |
| 44 | + |
| 45 | + # See if any of our hooks were deleted with the new commits |
| 46 | + hooks = set(repo.hooks.keys()) |
| 47 | + hooks_missing = hooks - (hooks & set(new_repo.manifest.hooks.keys())) |
| 48 | + if hooks_missing: |
| 49 | + raise RepositoryCannotBeUpdatedError( |
| 50 | + 'Cannot update because the tip of master is missing these hooks:\n' |
| 51 | + '{0}'.format(', '.join(sorted(hooks_missing))) |
| 52 | + ) |
| 53 | + |
| 54 | + return new_config |
| 55 | + |
| 56 | + |
| 57 | +def autoupdate(runner): |
| 58 | + """Auto-update the pre-commit config to the latest versions of repos.""" |
| 59 | + retv = 0 |
| 60 | + output_configs = [] |
| 61 | + changed = False |
| 62 | + |
| 63 | + input_configs = load_config( |
| 64 | + runner.config_file_path, |
| 65 | + load_strategy=ordered_load, |
| 66 | + ) |
| 67 | + |
| 68 | + for repo_config in input_configs: |
| 69 | + sys.stdout.write('Updating {0}...'.format(repo_config['repo'])) |
| 70 | + sys.stdout.flush() |
| 71 | + try: |
| 72 | + new_repo_config = _update_repository(repo_config, runner) |
| 73 | + except RepositoryCannotBeUpdatedError as error: |
| 74 | + print(error.args[0]) |
| 75 | + output_configs.append(repo_config) |
| 76 | + retv = 1 |
| 77 | + continue |
| 78 | + |
| 79 | + if new_repo_config['sha'] != repo_config['sha']: |
| 80 | + changed = True |
| 81 | + print( |
| 82 | + 'updating {0} -> {1}.'.format( |
| 83 | + repo_config['sha'], new_repo_config['sha'], |
| 84 | + ) |
| 85 | + ) |
| 86 | + output_configs.append(new_repo_config) |
| 87 | + else: |
| 88 | + print('already up to date.') |
| 89 | + output_configs.append(repo_config) |
| 90 | + |
| 91 | + if changed: |
| 92 | + with open(runner.config_file_path, 'w') as config_file: |
| 93 | + config_file.write( |
| 94 | + ordered_dump( |
| 95 | + remove_defaults(output_configs, CONFIG_JSON_SCHEMA), |
| 96 | + **C.YAML_DUMP_KWARGS |
| 97 | + ) |
| 98 | + ) |
| 99 | + |
| 100 | + return retv |
0 commit comments