import logging
import os.path
import sys
from typing import Dict
from typing import List
from typing import Optional
from typing import Set

from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.util import EnvironT


logger = logging.getLogger(__name__)


def zsplit(s: str) -> List[str]:
    s = s.strip('\0')
    if s:
        return s.split('\0')
    else:
        return []


def no_git_env(_env: Optional[EnvironT] = None) -> Dict[str, str]:
    # Too many bugs dealing with environment variables and GIT:
    # https://github.com/pre-commit/pre-commit/issues/300
    # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
    # pre-commit hooks
    # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
    # while running pre-commit hooks in submodules.
    # GIT_DIR: Causes git clone to clone wrong thing
    # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
    _env = _env if _env is not None else os.environ
    return {
        k: v for k, v in _env.items()
        if not k.startswith('GIT_') or
        k in {
            'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
            'GIT_SSL_NO_VERIFY',
        }
    }


def get_root() -> str:
    return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()


def get_git_dir(git_root: str = '.') -> str:
    opts = ('--git-common-dir', '--git-dir')
    _, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root)
    for line, opt in zip(out.splitlines(), opts):
        if line != opt:  # pragma: no branch (git < 2.5)
            return os.path.normpath(os.path.join(git_root, line))
    else:
        raise AssertionError('unreachable: no git dir')


def get_remote_url(git_root: str) -> str:
    _, out, _ = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)
    return out.strip()


def is_in_merge_conflict() -> bool:
    git_dir = get_git_dir('.')
    return (
        os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and
        os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))
    )


def parse_merge_msg_for_conflicts(merge_msg: bytes) -> List[str]:
    # Conflicted files start with tabs
    return [
        line.lstrip(b'#').strip().decode()
        for line in merge_msg.splitlines()
        # '#\t' for git 2.4.1
        if line.startswith((b'\t', b'#\t'))
    ]


def get_conflicted_files() -> Set[str]:
    logger.info('Checking merge-conflict files only.')
    # Need to get the conflicted files from the MERGE_MSG because they could
    # have resolved the conflict by choosing one side or the other
    with open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb') as f:
        merge_msg = f.read()
    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)

    # This will get the rest of the changes made after the merge.
    # If they resolved the merge conflict by choosing a mesh of both sides
    # this will also include the conflicted files
    tree_hash = cmd_output('git', 'write-tree')[1].strip()
    merge_diff_filenames = zsplit(
        cmd_output(
            'git', 'diff', '--name-only', '--no-ext-diff', '-z',
            '-m', tree_hash, 'HEAD', 'MERGE_HEAD',
        )[1],
    )
    return set(merge_conflict_filenames) | set(merge_diff_filenames)


def get_staged_files(cwd: Optional[str] = None) -> List[str]:
    return zsplit(
        cmd_output(
            'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',
            # Everything except for D
            '--diff-filter=ACMRTUXB',
            cwd=cwd,
        )[1],
    )


def intent_to_add_files() -> List[str]:
    _, stdout, _ = cmd_output('git', 'status', '--porcelain', '-z')
    parts = list(reversed(zsplit(stdout)))
    intent_to_add = []
    while parts:
        line = parts.pop()
        status, filename = line[:3], line[3:]
        if status[0] in {'C', 'R'}:  # renames / moves have an additional arg
            parts.pop()
        if status[1] == 'A':
            intent_to_add.append(filename)
    return intent_to_add


def get_all_files() -> List[str]:
    return zsplit(cmd_output('git', 'ls-files', '-z')[1])


def get_changed_files(old: str, new: str) -> List[str]:
    return zsplit(
        cmd_output(
            'git', 'diff', '--name-only', '--no-ext-diff', '-z',
            f'{old}...{new}',
        )[1],
    )


def head_rev(remote: str) -> str:
    _, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD')
    return out.split()[0]


def has_diff(*args: str, repo: str = '.') -> bool:
    cmd = ('git', 'diff', '--quiet', '--no-ext-diff', *args)
    return cmd_output_b(*cmd, cwd=repo, retcode=None)[0] == 1


def has_core_hookpaths_set() -> bool:
    _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', retcode=None)
    return bool(out.strip())


def init_repo(path: str, remote: str) -> None:
    if os.path.isdir(remote):
        remote = os.path.abspath(remote)

    env = no_git_env()
    cmd_output_b('git', 'init', path, env=env)
    cmd_output_b('git', 'remote', 'add', 'origin', remote, cwd=path, env=env)


def commit(repo: str = '.') -> None:
    env = no_git_env()
    name, email = 'pre-commit', 'asottile+pre-commit@umich.edu'
    env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = name
    env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = email
    cmd = ('git', 'commit', '--no-edit', '--no-gpg-sign', '-n', '-minit')
    cmd_output_b(*cmd, cwd=repo, env=env)


def git_path(name: str, repo: str = '.') -> str:
    _, out, _ = cmd_output('git', 'rev-parse', '--git-path', name, cwd=repo)
    return os.path.join(repo, out.strip())


def check_for_cygwin_mismatch() -> None:
    """See https://github.com/pre-commit/pre-commit/issues/354"""
    if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)
        is_cygwin_python = sys.platform == 'cygwin'
        toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]
        is_cygwin_git = toplevel.startswith('/')

        if is_cygwin_python ^ is_cygwin_git:
            exe_type = {True: '(cygwin)', False: '(windows)'}
            logger.warn(
                f'pre-commit has detected a mix of cygwin python / git\n'
                f'This combination is not supported, it is likely you will '
                f'receive an error later in the program.\n'
                f'Make sure to use cygwin git+python while using cygwin\n'
                f'These can be installed through the cygwin installer.\n'
                f' - python {exe_type[is_cygwin_python]}\n'
                f' - git {exe_type[is_cygwin_git]}\n',
            )

pre_commit/git.py
import logging
import os.path
import sys
from typing import Dict
from typing import List
from typing import Optional
from typing import Set

from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.util import EnvironT


logger = logging.getLogger(__name__)


def zsplit(s: str) -> List[str]:
    s = s.strip('\0')
    if s:
        return s.split('\0')
    else:
        return []


def no_git_env(_env: Optional[EnvironT] = None) -> Dict[str, str]:
    # Too many bugs dealing with environment variables and GIT:
    # https://github.com/pre-commit/pre-commit/issues/300
    # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
    # pre-commit hooks
    # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
    # while running pre-commit hooks in submodules.
    # GIT_DIR: Causes git clone to clone wrong thing
    # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
    _env = _env if _env is not None else os.environ
    return {
        k: v for k, v in _env.items()
        if not k.startswith('GIT_') or
        k in {
            'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
            'GIT_SSL_NO_VERIFY',
        }
    }


def get_root() -> str:
    return cmd_output('git', 'rev-parse', '--show-toplevel')[1].strip()


def get_git_dir(git_root: str = '.') -> str:
    opts = ('--git-common-dir', '--git-dir')
    _, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root)
    for line, opt in zip(out.splitlines(), opts):
        if line != opt:  # pragma: no branch (git < 2.5)
            return os.path.normpath(os.path.join(git_root, line))
    else:
        raise AssertionError('unreachable: no git dir')


def get_remote_url(git_root: str) -> str:
    _, out, _ = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)
    return out.strip()


def is_in_merge_conflict() -> bool:
    git_dir = get_git_dir('.')
    return (
        os.path.exists(os.path.join(git_dir, 'MERGE_MSG')) and
        os.path.exists(os.path.join(git_dir, 'MERGE_HEAD'))
    )


def parse_merge_msg_for_conflicts(merge_msg: bytes) -> List[str]:
    # Conflicted files start with tabs
    return [
        line.lstrip(b'#').strip().decode()
        for line in merge_msg.splitlines()
        # '#\t' for git 2.4.1
        if line.startswith((b'\t', b'#\t'))
    ]


def get_conflicted_files() -> Set[str]:
    logger.info('Checking merge-conflict files only.')
    # Need to get the conflicted files from the MERGE_MSG because they could
    # have resolved the conflict by choosing one side or the other
    with open(os.path.join(get_git_dir('.'), 'MERGE_MSG'), 'rb') as f:
        merge_msg = f.read()
    merge_conflict_filenames = parse_merge_msg_for_conflicts(merge_msg)

    # This will get the rest of the changes made after the merge.
    # If they resolved the merge conflict by choosing a mesh of both sides
    # this will also include the conflicted files
    tree_hash = cmd_output('git', 'write-tree')[1].strip()
    merge_diff_filenames = zsplit(
        cmd_output(
            'git', 'diff', '--name-only', '--no-ext-diff', '-z',
            '-m', tree_hash, 'HEAD', 'MERGE_HEAD',
        )[1],
    )
    return set(merge_conflict_filenames) | set(merge_diff_filenames)


def get_staged_files(cwd: Optional[str] = None) -> List[str]:
    return zsplit(
        cmd_output(
            'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',
            # Everything except for D
            '--diff-filter=ACMRTUXB',
            cwd=cwd,
        )[1],
    )


def intent_to_add_files() -> List[str]:
    _, stdout, _ = cmd_output('git', 'status', '--porcelain', '-z')
    parts = list(reversed(zsplit(stdout)))
    intent_to_add = []
    while parts:
        line = parts.pop()
        status, filename = line[:3], line[3:]
        if status[0] in {'C', 'R'}:  # renames / moves have an additional arg
            parts.pop()
        if status[1] == 'A':
            intent_to_add.append(filename)
    return intent_to_add


def get_all_files() -> List[str]:
    return zsplit(cmd_output('git', 'ls-files', '-z')[1])


def get_changed_files(old: str, new: str) -> List[str]:
    return zsplit(
        cmd_output(
            'git', 'diff', '--name-only', '--no-ext-diff', '-z',
            f'{old}...{new}',
        )[1],
    )


def head_rev(remote: str) -> str:
    _, out, _ = cmd_output('git', 'ls-remote', '--exit-code', remote, 'HEAD')
    return out.split()[0]


def has_diff(*args: str, repo: str = '.') -> bool:
    cmd = ('git', 'diff', '--quiet', '--no-ext-diff', *args)
    return cmd_output_b(*cmd, cwd=repo, retcode=None)[0] == 1


def has_core_hookpaths_set() -> bool:
    _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', retcode=None)
    return bool(out.strip())


def init_repo(path: str, remote: str) -> None:
    if os.path.isdir(remote):
        remote = os.path.abspath(remote)

    env = no_git_env()
    # avoid the user's template so that hooks do not recurse
    cmd_output_b('git', 'init', '--template=', path, env=env)
    cmd_output_b('git', 'remote', 'add', 'origin', remote, cwd=path, env=env)


def commit(repo: str = '.') -> None:
    env = no_git_env()
    name, email = 'pre-commit', 'asottile+pre-commit@umich.edu'
    env['GIT_AUTHOR_NAME'] = env['GIT_COMMITTER_NAME'] = name
    env['GIT_AUTHOR_EMAIL'] = env['GIT_COMMITTER_EMAIL'] = email
    cmd = ('git', 'commit', '--no-edit', '--no-gpg-sign', '-n', '-minit')
    cmd_output_b(*cmd, cwd=repo, env=env)


def git_path(name: str, repo: str = '.') -> str:
    _, out, _ = cmd_output('git', 'rev-parse', '--git-path', name, cwd=repo)
    return os.path.join(repo, out.strip())


def check_for_cygwin_mismatch() -> None:
    """See https://github.com/pre-commit/pre-commit/issues/354"""
    if sys.platform in ('cygwin', 'win32'):  # pragma: no cover (windows)
        is_cygwin_python = sys.platform == 'cygwin'
        toplevel = cmd_output('git', 'rev-parse', '--show-toplevel')[1]
        is_cygwin_git = toplevel.startswith('/')

        if is_cygwin_python ^ is_cygwin_git:
            exe_type = {True: '(cygwin)', False: '(windows)'}
            logger.warn(
                f'pre-commit has detected a mix of cygwin python / git\n'
                f'This combination is not supported, it is likely you will '
                f'receive an error later in the program.\n'
                f'Make sure to use cygwin git+python while using cygwin\n'
                f'These can be installed through the cygwin installer.\n'
                f' - python {exe_type[is_cygwin_python]}\n'
                f' - git {exe_type[is_cygwin_git]}\n',
            )

pre_commit/git.py
diff --git a/pre_commit/git.py b/pre_commit/git.py
index 7e757f247..576bef8cc 100644
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -158,7 +158,8 @@ def init_repo(path: str, remote: str) -> None:
         remote = os.path.abspath(remote)
 
     env = no_git_env()
-    cmd_output_b('git', 'init', path, env=env)
+    # avoid the user's template so that hooks do not recurse
+    cmd_output_b('git', 'init', '--template=', path, env=env)
     cmd_output_b('git', 'remote', 'add', 'origin', remote, cwd=path, env=env)
 
 
diff --git a/tests/git_test.py b/tests/git_test.py
index e73a6f240..fafd4a6e3 100644
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -186,3 +186,8 @@ def test_no_git_env():
         'GIT_SSH': '/usr/bin/ssh',
         'GIT_SSH_COMMAND': 'ssh -o',
     }
+
+
+def test_init_repo_no_hooks(tmpdir):
+    git.init_repo(str(tmpdir), remote='dne')
+    assert not tmpdir.join('.git/hooks').exists()

Stalls on hooks checkout
When trying to use some hook repositories I am getting hangs while fetching hooks. Tried pre-commit clean and rm -rf ~/.cache/pre-commit, but nothing changed. This is happening on both Windows 10 and Ubuntu 18.04.4 LTS.

pre-commit.log

> ### version information
> 
> ```
> pre-commit version: 2.4.0
> sys.version:
>     3.6.9 (default, Apr 18 2020, 01:56:04)
>     [GCC 8.4.0]
> sys.executable: /usr/bin/python3
> os.name: posix
> sys.platform: linux
> ```
> 
> ### error information
> 
> ```
> Interrupted (^C): KeyboardInterrupt:
> ```
> 
> ```
> Traceback (most recent call last):
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/error_handler.py", line 56, in error_handler
>     yield
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/main.py", line 388, in main
>     return install_hooks(args.config, store)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/commands/install_uninstall.py", line 153, in install_hooks
>     install_hook_envs(all_hooks(load_config(config_file), store), store)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/repository.py", line 206, in all_hooks
>     for repo in root_config['repos']
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/repository.py", line 207, in <genexpr>
>     for hook in _repository_hooks(repo, store, root_config)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/repository.py", line 182, in _repository_hooks
>     return _cloned_repository_hooks(repo_config, store, root_config)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/repository.py", line 148, in _cloned_repository_hooks
>     manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/store.py", line 183, in clone
>     return self._new_repo(repo, ref, deps, clone_strategy)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/store.py", line 140, in _new_repo
>     make_strategy(directory)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/store.py", line 179, in clone_strategy
>     self._shallow_clone(ref, _git_cmd)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/store.py", line 162, in _shallow_clone
>     git_cmd('checkout', 'FETCH_HEAD')
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/store.py", line 176, in _git_cmd
>     cmd_output_b('git', *args, cwd=directory, env=env)
>   File "/home/morgana/.local/lib/python3.6/site-packages/pre_commit/util.py", line 155, in cmd_output_b
>     stdout_b, stderr_b = proc.communicate()
>   File "/usr/lib/python3.6/subprocess.py", line 863, in communicate
>     stdout, stderr = self._communicate(input, endtime, timeout)
>   File "/usr/lib/python3.6/subprocess.py", line 1534, in _communicate
>     ready = selector.select(timeout)
>   File "/usr/lib/python3.6/selectors.py", line 376, in select
>     fd_event_list = self._poll.poll(timeout)
> KeyboardInterrupt

Added to  ~/.local/lib/python3.6/site-packages/pre_commit/util.py line 143

```python
print(cmd)
print(kwargs)
```

> $ pre-commit install-hooks
> ('git', 'rev-parse', '--show-toplevel')
> {'stdin': -1, 'stdout': -1, 'stderr': -1}
> [INFO] Initializing environment for https://github.com/psf/black.
> ('git', 'init', '/home/morgana/.cache/pre-commit/repohg1s5b7b')
> {'env': {'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', 'SSH_CONNECTION': '10.30.48.92 50335 10.30.48.87 22', 'LESSCLOSE': '/usr/bin/lesspipe %s %s', 'LANG': 'en_US.UTF-8', 'PERL_MB_OPT': '--install_base "/home/morgana/perl5"', 'XDG_SESSION_ID': '10', 'USER': 'morgana', 'PWD': '/home/morgana/projects/git-hooks', 'HOME': '/home/morgana', 'SSH_CLIENT': '10.30.48.92 50335 22', 'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop', 'PERL_MM_OPT': 'INSTALL_BASE=/home/morgana/perl5', 'PERL_LOCAL_LIB_ROOT': '/home/morgana/perl5', 'SSH_TTY': '/dev/pts/0', 'MAIL': '/var/mail/morgana', 'TERM': 'xterm', 'SHELL': '/bin/bash', 'PERL5LIB': '/home/morgana/perl5/lib/perl5', 'SHLVL': '1', 'LANGUAGE': 'en_US:', 'MANPATH': '/home/morgana/.git/subrepo/man:', 'LOGNAME': 'morgana', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/17173/bus', 'XDG_RUNTIME_DIR': '/run/user/17173', 'PATH': '/home/morgana/.local/bin:/home/morgana/bin:/home/morgana/perl5/bin:/home/morgana/.git/subrepo/lib:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin', 'LESSOPEN': '| /usr/bin/lesspipe %s', 'OLDPWD': '/home/morgana', '_': '/home/morgana/.local/bin/pre-commit'}, 'stdin': -1, 'stdout': -1, 'stderr': -1}
> ('git', 'remote', 'add', 'origin', 'https://github.com/psf/black')
> {'cwd': '/home/morgana/.cache/pre-commit/repohg1s5b7b', 'env': {'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', 'SSH_CONNECTION': '10.30.48.92 50335 10.30.48.87 22', 'LESSCLOSE': '/usr/bin/lesspipe %s %s', 'LANG': 'en_US.UTF-8', 'PERL_MB_OPT': '--install_base "/home/morgana/perl5"', 'XDG_SESSION_ID': '10', 'USER': 'morgana', 'PWD': '/home/morgana/projects/git-hooks', 'HOME': '/home/morgana', 'SSH_CLIENT': '10.30.48.92 50335 22', 'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop', 'PERL_MM_OPT': 'INSTALL_BASE=/home/morgana/perl5', 'PERL_LOCAL_LIB_ROOT': '/home/morgana/perl5', 'SSH_TTY': '/dev/pts/0', 'MAIL': '/var/mail/morgana', 'TERM': 'xterm', 'SHELL': '/bin/bash', 'PERL5LIB': '/home/morgana/perl5/lib/perl5', 'SHLVL': '1', 'LANGUAGE': 'en_US:', 'MANPATH': '/home/morgana/.git/subrepo/man:', 'LOGNAME': 'morgana', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/17173/bus', 'XDG_RUNTIME_DIR': '/run/user/17173', 'PATH': '/home/morgana/.local/bin:/home/morgana/bin:/home/morgana/perl5/bin:/home/morgana/.git/subrepo/lib:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin', 'LESSOPEN': '| /usr/bin/lesspipe %s', 'OLDPWD': '/home/morgana', '_': '/home/morgana/.local/bin/pre-commit'}, 'stdin': -1, 'stdout': -1, 'stderr': -1}
> ('git', '-c', 'protocol.version=2', 'fetch', 'origin', 'stable', '--depth=1')
> {'cwd': '/home/morgana/.cache/pre-commit/repohg1s5b7b', 'env': {'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', 'SSH_CONNECTION': '10.30.48.92 50335 10.30.48.87 22', 'LESSCLOSE': '/usr/bin/lesspipe %s %s', 'LANG': 'en_US.UTF-8', 'PERL_MB_OPT': '--install_base "/home/morgana/perl5"', 'XDG_SESSION_ID': '10', 'USER': 'morgana', 'PWD': '/home/morgana/projects/git-hooks', 'HOME': '/home/morgana', 'SSH_CLIENT': '10.30.48.92 50335 22', 'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop', 'PERL_MM_OPT': 'INSTALL_BASE=/home/morgana/perl5', 'PERL_LOCAL_LIB_ROOT': '/home/morgana/perl5', 'SSH_TTY': '/dev/pts/0', 'MAIL': '/var/mail/morgana', 'TERM': 'xterm', 'SHELL': '/bin/bash', 'PERL5LIB': '/home/morgana/perl5/lib/perl5', 'SHLVL': '1', 'LANGUAGE': 'en_US:', 'MANPATH': '/home/morgana/.git/subrepo/man:', 'LOGNAME': 'morgana', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/17173/bus', 'XDG_RUNTIME_DIR': '/run/user/17173', 'PATH': '/home/morgana/.local/bin:/home/morgana/bin:/home/morgana/perl5/bin:/home/morgana/.git/subrepo/lib:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin', 'LESSOPEN': '| /usr/bin/lesspipe %s', 'OLDPWD': '/home/morgana', '_': '/home/morgana/.local/bin/pre-commit'}, 'stdin': -1, 'stdout': -1, 'stderr': -1}
> ('git', 'checkout', 'FETCH_HEAD')
> {'cwd': '/home/morgana/.cache/pre-commit/repohg1s5b7b', 'env': {'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:', 'SSH_CONNECTION': '10.30.48.92 50335 10.30.48.87 22', 'LESSCLOSE': '/usr/bin/lesspipe %s %s', 'LANG': 'en_US.UTF-8', 'PERL_MB_OPT': '--install_base "/home/morgana/perl5"', 'XDG_SESSION_ID': '10', 'USER': 'morgana', 'PWD': '/home/morgana/projects/git-hooks', 'HOME': '/home/morgana', 'SSH_CLIENT': '10.30.48.92 50335 22', 'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop', 'PERL_MM_OPT': 'INSTALL_BASE=/home/morgana/perl5', 'PERL_LOCAL_LIB_ROOT': '/home/morgana/perl5', 'SSH_TTY': '/dev/pts/0', 'MAIL': '/var/mail/morgana', 'TERM': 'xterm', 'SHELL': '/bin/bash', 'PERL5LIB': '/home/morgana/perl5/lib/perl5', 'SHLVL': '1', 'LANGUAGE': 'en_US:', 'MANPATH': '/home/morgana/.git/subrepo/man:', 'LOGNAME': 'morgana', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/17173/bus', 'XDG_RUNTIME_DIR': '/run/user/17173', 'PATH': '/home/morgana/.local/bin:/home/morgana/bin:/home/morgana/perl5/bin:/home/morgana/.git/subrepo/lib:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin', 'LESSOPEN': '| /usr/bin/lesspipe %s', 'OLDPWD': '/home/morgana', '_': '/home/morgana/.local/bin/pre-commit'}, 'stdin': -1, 'stdout': -1, 'stderr': -1}

.pre-commit-config.yaml

```yaml
repos:
  - repo: https://github.com/psf/black
    rev: stable
    hooks:
      - id: black
        language_version: python3.6
        stages: [commit]
  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: v0.7.1.1
    hooks:
    -   id: shellcheck
        stages: [commit]
  - repo: local
    hooks:
      # Add a ChangeID tag to commit messages that don't have one
      -   id: Change-Id
          name: Add a ChangeID tag to commit messages that don't have one
          entry: change-id
          always_run: true
          language: script
          stages: [commit-msg]
```
