Skip to content

Commit 2c88791

Browse files
committed
Add pre-commit try-repo
`try-repo` is useful for: - Trying out a remote hook repository without needing to configure it. - Testing a hook repository while developing it.
1 parent e8641ee commit 2c88791

File tree

15 files changed

+254
-110
lines changed

15 files changed

+254
-110
lines changed

pre_commit/commands/try_repo.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from __future__ import absolute_import
2+
from __future__ import unicode_literals
3+
4+
import collections
5+
import os.path
6+
7+
from aspy.yaml import ordered_dump
8+
9+
import pre_commit.constants as C
10+
from pre_commit import git
11+
from pre_commit import output
12+
from pre_commit.commands.run import run
13+
from pre_commit.manifest import Manifest
14+
from pre_commit.runner import Runner
15+
from pre_commit.store import Store
16+
from pre_commit.util import tmpdir
17+
18+
19+
def try_repo(args):
20+
ref = args.ref or git.head_sha(args.repo)
21+
22+
with tmpdir() as tempdir:
23+
if args.hook:
24+
hooks = [{'id': args.hook}]
25+
else:
26+
manifest = Manifest(Store(tempdir).clone(args.repo, ref))
27+
hooks = [{'id': hook_id} for hook_id in sorted(manifest.hooks)]
28+
29+
items = (('repo', args.repo), ('sha', ref), ('hooks', hooks))
30+
config = {'repos': [collections.OrderedDict(items)]}
31+
config_s = ordered_dump(config, **C.YAML_DUMP_KWARGS)
32+
33+
config_filename = os.path.join(tempdir, C.CONFIG_FILE)
34+
with open(config_filename, 'w') as cfg:
35+
cfg.write(config_s)
36+
37+
output.write_line('=' * 79)
38+
output.write_line('Using config:')
39+
output.write_line('=' * 79)
40+
output.write(config_s)
41+
output.write_line('=' * 79)
42+
43+
runner = Runner('.', config_filename, store_dir=tempdir)
44+
return run(runner, args)

pre_commit/git.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ def get_changed_files(new, old):
9797
)[1])
9898

9999

100+
def head_sha(remote):
101+
_, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD')
102+
return out.split()[0]
103+
104+
100105
def check_for_cygwin_mismatch():
101106
"""See https://github.com/pre-commit/pre-commit/issues/354"""
102107
if sys.platform in ('cygwin', 'win32'): # pragma: no cover (windows)

pre_commit/main.py

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pre_commit.commands.migrate_config import migrate_config
1818
from pre_commit.commands.run import run
1919
from pre_commit.commands.sample_config import sample_config
20+
from pre_commit.commands.try_repo import try_repo
2021
from pre_commit.error_handler import error_handler
2122
from pre_commit.logging_handler import add_logging_handler
2223
from pre_commit.runner import Runner
@@ -53,6 +54,41 @@ def _add_hook_type_option(parser):
5354
)
5455

5556

57+
def _add_run_options(parser):
58+
parser.add_argument('hook', nargs='?', help='A single hook-id to run')
59+
parser.add_argument('--verbose', '-v', action='store_true', default=False)
60+
parser.add_argument(
61+
'--origin', '-o',
62+
help="The origin branch's commit_id when using `git push`.",
63+
)
64+
parser.add_argument(
65+
'--source', '-s',
66+
help="The remote branch's commit_id when using `git push`.",
67+
)
68+
parser.add_argument(
69+
'--commit-msg-filename',
70+
help='Filename to check when running during `commit-msg`',
71+
)
72+
parser.add_argument(
73+
'--hook-stage', choices=('commit', 'push', 'commit-msg'),
74+
default='commit',
75+
help='The stage during which the hook is fired e.g. commit or push.',
76+
)
77+
parser.add_argument(
78+
'--show-diff-on-failure', action='store_true',
79+
help='When hooks fail, run `git diff` directly afterward.',
80+
)
81+
mutex_group = parser.add_mutually_exclusive_group(required=False)
82+
mutex_group.add_argument(
83+
'--all-files', '-a', action='store_true', default=False,
84+
help='Run on all the files in the repo.',
85+
)
86+
mutex_group.add_argument(
87+
'--files', nargs='*', default=[],
88+
help='Specific filenames to run hooks on.',
89+
)
90+
91+
5692
def main(argv=None):
5793
argv = argv if argv is not None else sys.argv[1:]
5894
argv = [five.to_text(arg) for arg in argv]
@@ -142,47 +178,32 @@ def main(argv=None):
142178
run_parser = subparsers.add_parser('run', help='Run hooks.')
143179
_add_color_option(run_parser)
144180
_add_config_option(run_parser)
145-
run_parser.add_argument('hook', nargs='?', help='A single hook-id to run')
146-
run_parser.add_argument(
147-
'--verbose', '-v', action='store_true', default=False,
148-
)
149-
run_parser.add_argument(
150-
'--origin', '-o',
151-
help="The origin branch's commit_id when using `git push`.",
152-
)
153-
run_parser.add_argument(
154-
'--source', '-s',
155-
help="The remote branch's commit_id when using `git push`.",
156-
)
157-
run_parser.add_argument(
158-
'--commit-msg-filename',
159-
help='Filename to check when running during `commit-msg`',
160-
)
161-
run_parser.add_argument(
162-
'--hook-stage', choices=('commit', 'push', 'commit-msg'),
163-
default='commit',
164-
help='The stage during which the hook is fired e.g. commit or push.',
165-
)
166-
run_parser.add_argument(
167-
'--show-diff-on-failure', action='store_true',
168-
help='When hooks fail, run `git diff` directly afterward.',
169-
)
170-
run_mutex_group = run_parser.add_mutually_exclusive_group(required=False)
171-
run_mutex_group.add_argument(
172-
'--all-files', '-a', action='store_true', default=False,
173-
help='Run on all the files in the repo.',
174-
)
175-
run_mutex_group.add_argument(
176-
'--files', nargs='*', default=[],
177-
help='Specific filenames to run hooks on.',
178-
)
181+
_add_run_options(run_parser)
179182

180183
sample_config_parser = subparsers.add_parser(
181184
'sample-config', help='Produce a sample {} file'.format(C.CONFIG_FILE),
182185
)
183186
_add_color_option(sample_config_parser)
184187
_add_config_option(sample_config_parser)
185188

189+
try_repo_parser = subparsers.add_parser(
190+
'try-repo',
191+
help='Try the hooks in a repository, useful for developing new hooks.',
192+
)
193+
_add_color_option(try_repo_parser)
194+
_add_config_option(try_repo_parser)
195+
try_repo_parser.add_argument(
196+
'repo', help='Repository to source hooks from.',
197+
)
198+
try_repo_parser.add_argument(
199+
'--ref',
200+
help=(
201+
'Manually select a ref to run against, otherwise the `HEAD` '
202+
'revision will be used.'
203+
),
204+
)
205+
_add_run_options(try_repo_parser)
206+
186207
help = subparsers.add_parser(
187208
'help', help='Show help for a specific command.',
188209
)
@@ -231,6 +252,8 @@ def main(argv=None):
231252
return run(runner, args)
232253
elif args.command == 'sample-config':
233254
return sample_config()
255+
elif args.command == 'try-repo':
256+
return try_repo(args)
234257
else:
235258
raise NotImplementedError(
236259
'Command {} not implemented.'.format(args.command),

pre_commit/manifest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@
1414

1515

1616
class Manifest(object):
17-
def __init__(self, repo_path, repo_url):
17+
def __init__(self, repo_path):
1818
self.repo_path = repo_path
19-
self.repo_url = repo_url
2019

2120
@cached_property
2221
def manifest_contents(self):

pre_commit/repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def _cmd_runner_from_deps(self, language_name, deps):
146146

147147
@cached_property
148148
def manifest(self):
149-
return Manifest(self._repo_path, self.repo_config['repo'])
149+
return Manifest(self._repo_path)
150150

151151
@cached_property
152152
def hooks(self):

pre_commit/runner.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ class Runner(object):
1515
repository under test.
1616
"""
1717

18-
def __init__(self, git_root, config_file):
18+
def __init__(self, git_root, config_file, store_dir=None):
1919
self.git_root = git_root
2020
self.config_file = config_file
21+
self._store_dir = store_dir
2122

2223
@classmethod
2324
def create(cls, config_file):
24-
"""Creates a PreCommitRunner by doing the following:
25+
"""Creates a Runner by doing the following:
2526
- Finds the root of the current git repository
2627
- chdir to that directory
2728
"""
@@ -63,4 +64,4 @@ def pre_push_path(self):
6364

6465
@cached_property
6566
def store(self):
66-
return Store()
67+
return Store(self._store_dir)

testing/fixtures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
from aspy.yaml import ordered_load
1111

1212
import pre_commit.constants as C
13+
from pre_commit import git
1314
from pre_commit.clientlib import CONFIG_SCHEMA
1415
from pre_commit.clientlib import load_manifest
1516
from pre_commit.schema import apply_defaults
1617
from pre_commit.schema import validate
1718
from pre_commit.util import cmd_output
1819
from pre_commit.util import copy_tree_to_path
1920
from pre_commit.util import cwd
20-
from testing.util import get_head_sha
2121
from testing.util import get_resource_path
2222

2323

@@ -84,7 +84,7 @@ def make_config_from_repo(repo_path, sha=None, hooks=None, check=True):
8484
manifest = load_manifest(os.path.join(repo_path, C.MANIFEST_FILE))
8585
config = OrderedDict((
8686
('repo', 'file://{}'.format(repo_path)),
87-
('sha', sha or get_head_sha(repo_path)),
87+
('sha', sha or git.head_sha(repo_path)),
8888
(
8989
'hooks',
9090
hooks or [OrderedDict((('id', hook['id']),)) for hook in manifest],

testing/util.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pre_commit.languages.docker import docker_is_running
99
from pre_commit.languages.pcre import GREP
1010
from pre_commit.util import cmd_output
11-
from pre_commit.util import cwd
11+
from testing.auto_namedtuple import auto_namedtuple
1212

1313

1414
TESTING_DIR = os.path.abspath(os.path.dirname(__file__))
@@ -18,11 +18,6 @@ def get_resource_path(path):
1818
return os.path.join(TESTING_DIR, 'resources', path)
1919

2020

21-
def get_head_sha(dir):
22-
with cwd(dir):
23-
return cmd_output('git', 'rev-parse', 'HEAD')[1].strip()
24-
25-
2621
def cmd_output_mocked_pre_commit_home(*args, **kwargs):
2722
# keyword-only argument
2823
tempdir_factory = kwargs.pop('tempdir_factory')
@@ -72,3 +67,31 @@ def platform_supports_pcre():
7267
not hasattr(os, 'symlink'),
7368
reason='Symlink is not supported on this platform',
7469
)
70+
71+
72+
def run_opts(
73+
all_files=False,
74+
files=(),
75+
color=False,
76+
verbose=False,
77+
hook=None,
78+
origin='',
79+
source='',
80+
hook_stage='commit',
81+
show_diff_on_failure=False,
82+
commit_msg_filename='',
83+
):
84+
# These are mutually exclusive
85+
assert not (all_files and files)
86+
return auto_namedtuple(
87+
all_files=all_files,
88+
files=files,
89+
color=color,
90+
verbose=verbose,
91+
hook=hook,
92+
origin=origin,
93+
source=source,
94+
hook_stage=hook_stage,
95+
show_diff_on_failure=show_diff_on_failure,
96+
commit_msg_filename=commit_msg_filename,
97+
)

tests/commands/autoupdate_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pytest
88

99
import pre_commit.constants as C
10+
from pre_commit import git
1011
from pre_commit.clientlib import load_config
1112
from pre_commit.commands.autoupdate import _update_repo
1213
from pre_commit.commands.autoupdate import autoupdate
@@ -21,7 +22,6 @@
2122
from testing.fixtures import make_config_from_repo
2223
from testing.fixtures import make_repo
2324
from testing.fixtures import write_config
24-
from testing.util import get_head_sha
2525
from testing.util import get_resource_path
2626

2727

@@ -66,10 +66,10 @@ def test_autoupdate_old_revision_broken(
6666
cmd_output('git', 'mv', C.MANIFEST_FILE, 'nope.yaml')
6767
cmd_output('git', 'commit', '-m', 'simulate old repo')
6868
# Assume this is the revision the user's old repository was at
69-
rev = get_head_sha(path)
69+
rev = git.head_sha(path)
7070
cmd_output('git', 'mv', 'nope.yaml', C.MANIFEST_FILE)
7171
cmd_output('git', 'commit', '-m', 'move hooks file')
72-
update_rev = get_head_sha(path)
72+
update_rev = git.head_sha(path)
7373

7474
config['sha'] = rev
7575
write_config('.', config)
@@ -84,12 +84,12 @@ def test_autoupdate_old_revision_broken(
8484
@pytest.yield_fixture
8585
def out_of_date_repo(tempdir_factory):
8686
path = make_repo(tempdir_factory, 'python_hooks_repo')
87-
original_sha = get_head_sha(path)
87+
original_sha = git.head_sha(path)
8888

8989
# Make a commit
9090
with cwd(path):
9191
cmd_output('git', 'commit', '--allow-empty', '-m', 'foo')
92-
head_sha = get_head_sha(path)
92+
head_sha = git.head_sha(path)
9393

9494
yield auto_namedtuple(
9595
path=path, original_sha=original_sha, head_sha=head_sha,
@@ -225,7 +225,7 @@ def test_autoupdate_tags_only(
225225
@pytest.yield_fixture
226226
def hook_disappearing_repo(tempdir_factory):
227227
path = make_repo(tempdir_factory, 'python_hooks_repo')
228-
original_sha = get_head_sha(path)
228+
original_sha = git.head_sha(path)
229229

230230
with cwd(path):
231231
shutil.copy(

0 commit comments

Comments
 (0)