44import os
55import pkg_resources
66import 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
917def 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
0 commit comments