Merging upstream version 2.16.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
2b79a68a34
commit
ada93679f9
34 changed files with 301 additions and 126 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,2 +0,0 @@
|
||||||
github: asottile
|
|
||||||
open_collective: pre-commit
|
|
41
.github/ISSUE_TEMPLATE/bug.yaml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug.yaml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
name: bug report
|
||||||
|
description: something went wrong
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
this is for issues for `pre-commit` (the framework).
|
||||||
|
if you are reporting an issue for [pre-commit.ci] please report it at [pre-commit-ci/issues]
|
||||||
|
|
||||||
|
[pre-commit.ci]: https://pre-commit.ci
|
||||||
|
[pre-commit-ci/issues]: https://github.com/pre-commit-ci/issues
|
||||||
|
- type: textarea
|
||||||
|
id: freeform
|
||||||
|
attributes:
|
||||||
|
label: describe your issue
|
||||||
|
placeholder: 'I was doing ... I ran ... I expected ... I got ...'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: pre-commit --version
|
||||||
|
placeholder: pre-commit x.x.x
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: configuration
|
||||||
|
attributes:
|
||||||
|
label: .pre-commit-config.yaml
|
||||||
|
description: (auto-rendered as yaml, no need for backticks)
|
||||||
|
placeholder: 'repos: ...'
|
||||||
|
render: yaml
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: error-log
|
||||||
|
attributes:
|
||||||
|
label: '~/.cache/pre-commit/pre-commit.log (if present)'
|
||||||
|
placeholder: "### version information\n..."
|
||||||
|
validations:
|
||||||
|
required: false
|
|
@ -12,7 +12,7 @@ repos:
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- id: double-quote-string-fixer
|
- id: double-quote-string-fixer
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 3.9.2
|
rev: 4.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies: [flake8-typing-imports==1.10.0]
|
additional_dependencies: [flake8-typing-imports==1.10.0]
|
||||||
|
@ -21,11 +21,11 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: autopep8
|
- id: autopep8
|
||||||
- repo: https://github.com/pre-commit/pre-commit
|
- repo: https://github.com/pre-commit/pre-commit
|
||||||
rev: v2.15.0
|
rev: v2.16.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: validate_manifest
|
- id: validate_manifest
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.25.0
|
rev: v2.29.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py36-plus]
|
args: [--py36-plus]
|
||||||
|
@ -35,16 +35,16 @@ repos:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
args: [--py3-plus]
|
args: [--py3-plus]
|
||||||
- repo: https://github.com/asottile/add-trailing-comma
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
rev: v2.1.0
|
rev: v2.2.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: add-trailing-comma
|
- id: add-trailing-comma
|
||||||
args: [--py36-plus]
|
args: [--py36-plus]
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||||
rev: v1.17.0
|
rev: v1.20.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.910
|
rev: v0.910-1
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
additional_dependencies: [types-all]
|
additional_dependencies: [types-all]
|
||||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -1,3 +1,33 @@
|
||||||
|
2.16.0 - 2021-11-30
|
||||||
|
===================
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- add warning for regexes containing `[\/]` or `[/\\]`.
|
||||||
|
- #2053 PR by @radek-sprta.
|
||||||
|
- #2043 issue by @asottile.
|
||||||
|
- move hook template back to `bash` resolving shebang-portability issues.
|
||||||
|
- #2065 PR by @asottile.
|
||||||
|
- add support for `fail_fast` at the individual hook level.
|
||||||
|
- #2097 PR by @colens3.
|
||||||
|
- #1143 issue by @potiuk.
|
||||||
|
- allow passthrough of `GIT_CONFIG_KEY_*`, `GIT_CONFIG_VALUE_*`, and
|
||||||
|
`GIT_CONFIG_COUNT`.
|
||||||
|
- #2136 PR by @emzeat.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- fix pre-commit autoupdate for `core.useBuiltinFSMonitor=true` on windows.
|
||||||
|
- #2047 PR by @asottile.
|
||||||
|
- #2046 issue by @lcnittl.
|
||||||
|
- fix temporary file stashing with for `submodule.recurse=1`.
|
||||||
|
- #2071 PR by @asottile.
|
||||||
|
- #2063 issue by @a666.
|
||||||
|
- ban broken importlib-resources versions.
|
||||||
|
- #2098 PR by @asottile.
|
||||||
|
- replace `exit(...)` with `raise SystemExit(...)` for portability.
|
||||||
|
- #2103 PR by @asottile.
|
||||||
|
- #2104 PR by @asottile.
|
||||||
|
|
||||||
|
|
||||||
2.15.0 - 2021-09-02
|
2.15.0 - 2021-09-02
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,4 @@ from pre_commit.main import main
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -70,6 +70,7 @@ MANIFEST_HOOK_DICT = cfgv.Map(
|
||||||
),
|
),
|
||||||
cfgv.Optional('args', cfgv.check_array(cfgv.check_string), []),
|
cfgv.Optional('args', cfgv.check_array(cfgv.check_string), []),
|
||||||
cfgv.Optional('always_run', cfgv.check_bool, False),
|
cfgv.Optional('always_run', cfgv.check_bool, False),
|
||||||
|
cfgv.Optional('fail_fast', cfgv.check_bool, False),
|
||||||
cfgv.Optional('pass_filenames', cfgv.check_bool, True),
|
cfgv.Optional('pass_filenames', cfgv.check_bool, True),
|
||||||
cfgv.Optional('description', cfgv.check_string, ''),
|
cfgv.Optional('description', cfgv.check_string, ''),
|
||||||
cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT),
|
cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT),
|
||||||
|
@ -143,6 +144,18 @@ class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault):
|
||||||
f"regex, not a glob -- matching '/*' probably isn't what you "
|
f"regex, not a glob -- matching '/*' probably isn't what you "
|
||||||
f'want here',
|
f'want here',
|
||||||
)
|
)
|
||||||
|
if r'[\/]' in dct.get(self.key, ''):
|
||||||
|
logger.warning(
|
||||||
|
fr'pre-commit normalizes slashes in the {self.key!r} field '
|
||||||
|
fr'in hook {dct.get("id")!r} to forward slashes, so you '
|
||||||
|
fr'can use / instead of [\/]',
|
||||||
|
)
|
||||||
|
if r'[/\\]' in dct.get(self.key, ''):
|
||||||
|
logger.warning(
|
||||||
|
fr'pre-commit normalizes slashes in the {self.key!r} field '
|
||||||
|
fr'in hook {dct.get("id")!r} to forward slashes, so you '
|
||||||
|
fr'can use / instead of [/\\]',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
|
class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
|
||||||
|
@ -154,6 +167,18 @@ class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
|
||||||
f'The top-level {self.key!r} field is a regex, not a glob -- '
|
f'The top-level {self.key!r} field is a regex, not a glob -- '
|
||||||
f"matching '/*' probably isn't what you want here",
|
f"matching '/*' probably isn't what you want here",
|
||||||
)
|
)
|
||||||
|
if r'[\/]' in dct.get(self.key, ''):
|
||||||
|
logger.warning(
|
||||||
|
fr'pre-commit normalizes the slashes in the top-level '
|
||||||
|
fr'{self.key!r} field to forward slashes, so you can use / '
|
||||||
|
fr'instead of [\/]',
|
||||||
|
)
|
||||||
|
if r'[/\\]' in dct.get(self.key, ''):
|
||||||
|
logger.warning(
|
||||||
|
fr'pre-commit normalizes the slashes in the top-level '
|
||||||
|
fr'{self.key!r} field to forward slashes, so you can use / '
|
||||||
|
fr'instead of [/\\]',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MigrateShaToRev:
|
class MigrateShaToRev:
|
||||||
|
|
|
@ -36,24 +36,36 @@ class RevInfo(NamedTuple):
|
||||||
return cls(config['repo'], config['rev'], None)
|
return cls(config['repo'], config['rev'], None)
|
||||||
|
|
||||||
def update(self, tags_only: bool, freeze: bool) -> 'RevInfo':
|
def update(self, tags_only: bool, freeze: bool) -> 'RevInfo':
|
||||||
|
git_cmd = ('git', *git.NO_FS_MONITOR)
|
||||||
|
|
||||||
if tags_only:
|
if tags_only:
|
||||||
tag_cmd = ('git', 'describe', 'FETCH_HEAD', '--tags', '--abbrev=0')
|
tag_cmd = (
|
||||||
|
*git_cmd, 'describe',
|
||||||
|
'FETCH_HEAD', '--tags', '--abbrev=0',
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
tag_cmd = ('git', 'describe', 'FETCH_HEAD', '--tags', '--exact')
|
tag_cmd = (
|
||||||
|
*git_cmd, 'describe',
|
||||||
|
'FETCH_HEAD', '--tags', '--exact',
|
||||||
|
)
|
||||||
|
|
||||||
with tmpdir() as tmp:
|
with tmpdir() as tmp:
|
||||||
git.init_repo(tmp, self.repo)
|
git.init_repo(tmp, self.repo)
|
||||||
cmd_output_b('git', 'fetch', 'origin', 'HEAD', '--tags', cwd=tmp)
|
cmd_output_b(
|
||||||
|
*git_cmd, 'fetch', 'origin', 'HEAD', '--tags',
|
||||||
|
cwd=tmp,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rev = cmd_output(*tag_cmd, cwd=tmp)[1].strip()
|
rev = cmd_output(*tag_cmd, cwd=tmp)[1].strip()
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
cmd = ('git', 'rev-parse', 'FETCH_HEAD')
|
cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD')
|
||||||
rev = cmd_output(*cmd, cwd=tmp)[1].strip()
|
rev = cmd_output(*cmd, cwd=tmp)[1].strip()
|
||||||
|
|
||||||
frozen = None
|
frozen = None
|
||||||
if freeze:
|
if freeze:
|
||||||
exact = cmd_output('git', 'rev-parse', rev, cwd=tmp)[1].strip()
|
exact_rev_cmd = (*git_cmd, 'rev-parse', rev)
|
||||||
|
exact = cmd_output(*exact_rev_cmd, cwd=tmp)[1].strip()
|
||||||
if exact != rev:
|
if exact != rev:
|
||||||
rev, frozen = exact, rev
|
rev, frozen = exact, rev
|
||||||
return self._replace(rev=rev, frozen=frozen)
|
return self._replace(rev=rev, frozen=frozen)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -100,19 +101,17 @@ def _install_hook_script(
|
||||||
args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}']
|
args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}']
|
||||||
if skip_on_missing_config:
|
if skip_on_missing_config:
|
||||||
args.append('--skip-on-missing-config')
|
args.append('--skip-on-missing-config')
|
||||||
params = {'INSTALL_PYTHON': sys.executable, 'ARGS': args}
|
|
||||||
|
|
||||||
with open(hook_path, 'w') as hook_file:
|
with open(hook_path, 'w') as hook_file:
|
||||||
contents = resource_text('hook-tmpl')
|
contents = resource_text('hook-tmpl')
|
||||||
before, rest = contents.split(TEMPLATE_START)
|
before, rest = contents.split(TEMPLATE_START)
|
||||||
to_template, after = rest.split(TEMPLATE_END)
|
_, after = rest.split(TEMPLATE_END)
|
||||||
|
|
||||||
before = before.replace('#!/usr/bin/env python3', shebang())
|
|
||||||
|
|
||||||
hook_file.write(before + TEMPLATE_START)
|
hook_file.write(before + TEMPLATE_START)
|
||||||
for line in to_template.splitlines():
|
hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n')
|
||||||
var = line.split()[0]
|
# TODO: python3.8+: shlex.join
|
||||||
hook_file.write(f'{var} = {params[var]!r}\n')
|
args_s = ' '.join(shlex.quote(part) for part in args)
|
||||||
|
hook_file.write(f'ARGS=({args_s})\n')
|
||||||
hook_file.write(TEMPLATE_END + after)
|
hook_file.write(TEMPLATE_END + after)
|
||||||
make_executable(hook_path)
|
make_executable(hook_path)
|
||||||
|
|
||||||
|
|
|
@ -290,7 +290,7 @@ def _run_hooks(
|
||||||
verbose=args.verbose, use_color=args.color,
|
verbose=args.verbose, use_color=args.color,
|
||||||
)
|
)
|
||||||
retval |= current_retval
|
retval |= current_retval
|
||||||
if retval and config['fail_fast']:
|
if retval and (config['fail_fast'] or hook.fail_fast):
|
||||||
break
|
break
|
||||||
if retval and args.show_diff_on_failure and prior_diff:
|
if retval and args.show_diff_on_failure and prior_diff:
|
||||||
if args.all_files:
|
if args.all_files:
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info < (3, 8): # pragma: no cover (<PY38)
|
if sys.version_info >= (3, 8): # pragma: >=3.8 cover
|
||||||
import importlib_metadata
|
|
||||||
else: # pragma: no cover (PY38+)
|
|
||||||
import importlib.metadata as importlib_metadata
|
import importlib.metadata as importlib_metadata
|
||||||
|
else: # pragma: <3.8 cover
|
||||||
|
import importlib_metadata
|
||||||
|
|
||||||
CONFIG_FILE = '.pre-commit-config.yaml'
|
CONFIG_FILE = '.pre-commit-config.yaml'
|
||||||
MANIFEST_FILE = '.pre-commit-hooks.yaml'
|
MANIFEST_FILE = '.pre-commit-hooks.yaml'
|
||||||
|
|
|
@ -12,9 +12,11 @@ from pre_commit.util import CalledProcessError
|
||||||
from pre_commit.util import cmd_output
|
from pre_commit.util import cmd_output
|
||||||
from pre_commit.util import cmd_output_b
|
from pre_commit.util import cmd_output_b
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# see #2046
|
||||||
|
NO_FS_MONITOR = ('-c', 'core.useBuiltinFSMonitor=false')
|
||||||
|
|
||||||
|
|
||||||
def zsplit(s: str) -> List[str]:
|
def zsplit(s: str) -> List[str]:
|
||||||
s = s.strip('\0')
|
s = s.strip('\0')
|
||||||
|
@ -39,9 +41,10 @@ def no_git_env(
|
||||||
return {
|
return {
|
||||||
k: v for k, v in _env.items()
|
k: v for k, v in _env.items()
|
||||||
if not k.startswith('GIT_') or
|
if not k.startswith('GIT_') or
|
||||||
|
k.startswith(('GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_')) or
|
||||||
k in {
|
k in {
|
||||||
'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
|
'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
|
||||||
'GIT_SSL_NO_VERIFY',
|
'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,10 +188,11 @@ def init_repo(path: str, remote: str) -> None:
|
||||||
if os.path.isdir(remote):
|
if os.path.isdir(remote):
|
||||||
remote = os.path.abspath(remote)
|
remote = os.path.abspath(remote)
|
||||||
|
|
||||||
|
git = ('git', *NO_FS_MONITOR)
|
||||||
env = no_git_env()
|
env = no_git_env()
|
||||||
# avoid the user's template so that hooks do not recurse
|
# avoid the user's template so that hooks do not recurse
|
||||||
cmd_output_b('git', 'init', '--template=', path, env=env)
|
cmd_output_b(*git, 'init', '--template=', path, env=env)
|
||||||
cmd_output_b('git', 'remote', 'add', 'origin', remote, cwd=path, env=env)
|
cmd_output_b(*git, 'remote', 'add', 'origin', remote, cwd=path, env=env)
|
||||||
|
|
||||||
|
|
||||||
def commit(repo: str = '.') -> None:
|
def commit(repo: str = '.') -> None:
|
||||||
|
|
|
@ -27,6 +27,7 @@ class Hook(NamedTuple):
|
||||||
additional_dependencies: Sequence[str]
|
additional_dependencies: Sequence[str]
|
||||||
args: Sequence[str]
|
args: Sequence[str]
|
||||||
always_run: bool
|
always_run: bool
|
||||||
|
fail_fast: bool
|
||||||
pass_filenames: bool
|
pass_filenames: bool
|
||||||
description: str
|
description: str
|
||||||
language_version: str
|
language_version: str
|
||||||
|
|
|
@ -124,4 +124,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -411,4 +411,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -39,4 +39,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -77,4 +77,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -13,4 +13,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -1,44 +1,20 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env bash
|
||||||
# File generated by pre-commit: https://pre-commit.com
|
# File generated by pre-commit: https://pre-commit.com
|
||||||
# ID: 138fd403232d2ddd5efb44317e38bf03
|
# ID: 138fd403232d2ddd5efb44317e38bf03
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# we try our best, but the shebang of this script is difficult to determine:
|
|
||||||
# - macos doesn't ship with python3
|
|
||||||
# - windows executables are almost always `python.exe`
|
|
||||||
# therefore we continue to support python2 for this small script
|
|
||||||
if sys.version_info < (3, 3):
|
|
||||||
from distutils.spawn import find_executable as which
|
|
||||||
else:
|
|
||||||
from shutil import which
|
|
||||||
|
|
||||||
# work around https://github.com/Homebrew/homebrew-core/issues/30445
|
|
||||||
os.environ.pop('__PYVENV_LAUNCHER__', None)
|
|
||||||
|
|
||||||
# start templated
|
# start templated
|
||||||
INSTALL_PYTHON = ''
|
INSTALL_PYTHON=''
|
||||||
ARGS = ['hook-impl']
|
ARGS=(hook-impl)
|
||||||
# end templated
|
# end templated
|
||||||
ARGS.extend(('--hook-dir', os.path.realpath(os.path.dirname(__file__))))
|
|
||||||
ARGS.append('--')
|
|
||||||
ARGS.extend(sys.argv[1:])
|
|
||||||
|
|
||||||
DNE = '`pre-commit` not found. Did you forget to activate your virtualenv?'
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
if os.access(INSTALL_PYTHON, os.X_OK):
|
ARGS+=(--hook-dir "$HERE" -- "$@")
|
||||||
CMD = [INSTALL_PYTHON, '-mpre_commit']
|
|
||||||
elif which('pre-commit'):
|
|
||||||
CMD = ['pre-commit']
|
|
||||||
else:
|
|
||||||
raise SystemExit(DNE)
|
|
||||||
|
|
||||||
CMD.extend(ARGS)
|
if [ -x "$INSTALL_PYTHON" ]; then
|
||||||
if sys.platform == 'win32': # https://bugs.python.org/issue19124
|
exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
|
||||||
import subprocess
|
elif command -v pre-commit > /dev/null; then
|
||||||
|
exec pre-commit "${ARGS[@]}"
|
||||||
if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
|
else
|
||||||
raise SystemExit(subprocess.Popen(CMD).wait())
|
echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2
|
||||||
else:
|
exit 1
|
||||||
raise SystemExit(subprocess.call(CMD))
|
fi
|
||||||
else:
|
|
||||||
os.execvp(CMD[0], CMD)
|
|
||||||
|
|
|
@ -13,6 +13,12 @@ from pre_commit.xargs import xargs
|
||||||
|
|
||||||
logger = logging.getLogger('pre_commit')
|
logger = logging.getLogger('pre_commit')
|
||||||
|
|
||||||
|
# without forcing submodule.recurse=0, changes in nested submodules will be
|
||||||
|
# discarded if `submodule.recurse=1` is configured
|
||||||
|
# we choose this instead of `--no-recurse-submodules` because it works on
|
||||||
|
# versions of git before that option was added to `git checkout`
|
||||||
|
_CHECKOUT_CMD = ('git', '-c', 'submodule.recurse=0', 'checkout', '--', '.')
|
||||||
|
|
||||||
|
|
||||||
def _git_apply(patch: str) -> None:
|
def _git_apply(patch: str) -> None:
|
||||||
args = ('apply', '--whitespace=nowarn', patch)
|
args = ('apply', '--whitespace=nowarn', patch)
|
||||||
|
@ -58,7 +64,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
|
||||||
|
|
||||||
# prevent recursive post-checkout hooks (#1418)
|
# prevent recursive post-checkout hooks (#1418)
|
||||||
no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1')
|
no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1')
|
||||||
cmd_output_b('git', 'checkout', '--', '.', env=no_checkout_env)
|
cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
@ -74,7 +80,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
|
||||||
# We failed to apply the patch, presumably due to fixes made
|
# We failed to apply the patch, presumably due to fixes made
|
||||||
# by hooks.
|
# by hooks.
|
||||||
# Roll back the changes made by hooks.
|
# Roll back the changes made by hooks.
|
||||||
cmd_output_b('git', 'checkout', '--', '.', env=no_checkout_env)
|
cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
|
||||||
_git_apply(patch_filename)
|
_git_apply(patch_filename)
|
||||||
|
|
||||||
logger.info(f'Restored changes from {patch_filename}.')
|
logger.info(f'Restored changes from {patch_filename}.')
|
||||||
|
|
|
@ -21,10 +21,10 @@ import yaml
|
||||||
|
|
||||||
from pre_commit import parse_shebang
|
from pre_commit import parse_shebang
|
||||||
|
|
||||||
if sys.version_info >= (3, 7): # pragma: no cover (PY37+)
|
if sys.version_info >= (3, 7): # pragma: >=3.7 cover
|
||||||
from importlib.resources import open_binary
|
from importlib.resources import open_binary
|
||||||
from importlib.resources import read_text
|
from importlib.resources import read_text
|
||||||
else: # pragma: no cover (<PY37)
|
else: # pragma: <3.7 cover
|
||||||
from importlib_resources import open_binary
|
from importlib_resources import open_binary
|
||||||
from importlib_resources import read_text
|
from importlib_resources import read_text
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
covdefaults
|
covdefaults>=2.1
|
||||||
coverage
|
coverage
|
||||||
distlib
|
distlib
|
||||||
pytest
|
pytest
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = pre_commit
|
name = pre_commit
|
||||||
version = 2.15.0
|
version = 2.16.0
|
||||||
description = A framework for managing and maintaining multi-language pre-commit hooks.
|
description = A framework for managing and maintaining multi-language pre-commit hooks.
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
|
@ -17,6 +17,7 @@ classifiers =
|
||||||
Programming Language :: Python :: 3.7
|
Programming Language :: Python :: 3.7
|
||||||
Programming Language :: Python :: 3.8
|
Programming Language :: Python :: 3.8
|
||||||
Programming Language :: Python :: 3.9
|
Programming Language :: Python :: 3.9
|
||||||
|
Programming Language :: Python :: 3.10
|
||||||
Programming Language :: Python :: Implementation :: CPython
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
Programming Language :: Python :: Implementation :: PyPy
|
Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ install_requires =
|
||||||
toml
|
toml
|
||||||
virtualenv>=20.0.8
|
virtualenv>=20.0.8
|
||||||
importlib-metadata;python_version<"3.8"
|
importlib-metadata;python_version<"3.8"
|
||||||
importlib-resources;python_version<"3.7"
|
importlib-resources<5.3;python_version<"3.7"
|
||||||
python_requires = >=3.6.1
|
python_requires = >=3.6.1
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
|
|
|
@ -25,4 +25,4 @@ def main() -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -80,4 +80,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -68,4 +68,4 @@ def main() -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -106,4 +106,4 @@ def main() -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -45,4 +45,4 @@ def main() -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
@ -247,38 +247,64 @@ def test_warn_mutable_rev_conditional():
|
||||||
cfgv.validate(config_obj, CONFIG_REPO_DICT)
|
cfgv.validate(config_obj, CONFIG_REPO_DICT)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_optional_sensible_regex_at_hook_level(caplog):
|
@pytest.mark.parametrize(
|
||||||
config_obj = {
|
('regex', 'warning'),
|
||||||
'id': 'flake8',
|
(
|
||||||
'files': 'dir/*.py',
|
|
||||||
}
|
|
||||||
cfgv.validate(config_obj, CONFIG_HOOK_DICT)
|
|
||||||
|
|
||||||
assert caplog.record_tuples == [
|
|
||||||
(
|
(
|
||||||
'pre_commit',
|
r'dir/*.py',
|
||||||
logging.WARNING,
|
|
||||||
"The 'files' field in hook 'flake8' is a regex, not a glob -- "
|
"The 'files' field in hook 'flake8' is a regex, not a glob -- "
|
||||||
"matching '/*' probably isn't what you want here",
|
"matching '/*' probably isn't what you want here",
|
||||||
),
|
),
|
||||||
]
|
(
|
||||||
|
r'dir[\/].*\.py',
|
||||||
|
r"pre-commit normalizes slashes in the 'files' field in hook "
|
||||||
def test_validate_optional_sensible_regex_at_top_level(caplog):
|
r"'flake8' to forward slashes, so you can use / instead of [\/]",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r'dir[/\\].*\.py',
|
||||||
|
r"pre-commit normalizes slashes in the 'files' field in hook "
|
||||||
|
r"'flake8' to forward slashes, so you can use / instead of [/\\]",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_validate_optional_sensible_regex_at_hook(caplog, regex, warning):
|
||||||
config_obj = {
|
config_obj = {
|
||||||
'files': 'dir/*.py',
|
'id': 'flake8',
|
||||||
|
'files': regex,
|
||||||
|
}
|
||||||
|
cfgv.validate(config_obj, CONFIG_HOOK_DICT)
|
||||||
|
|
||||||
|
assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
('regex', 'warning'),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
r'dir/*.py',
|
||||||
|
"The top-level 'files' field is a regex, not a glob -- "
|
||||||
|
"matching '/*' probably isn't what you want here",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r'dir[\/].*\.py',
|
||||||
|
r"pre-commit normalizes the slashes in the top-level 'files' "
|
||||||
|
r'field to forward slashes, so you can use / instead of [\/]',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
r'dir[/\\].*\.py',
|
||||||
|
r"pre-commit normalizes the slashes in the top-level 'files' "
|
||||||
|
r'field to forward slashes, so you can use / instead of [/\\]',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_validate_optional_sensible_regex_at_top_level(caplog, regex, warning):
|
||||||
|
config_obj = {
|
||||||
|
'files': regex,
|
||||||
'repos': [],
|
'repos': [],
|
||||||
}
|
}
|
||||||
cfgv.validate(config_obj, CONFIG_SCHEMA)
|
cfgv.validate(config_obj, CONFIG_SCHEMA)
|
||||||
|
|
||||||
assert caplog.record_tuples == [
|
assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)]
|
||||||
(
|
|
||||||
'pre_commit',
|
|
||||||
logging.WARNING,
|
|
||||||
"The top-level 'files' field is a regex, not a glob -- matching "
|
|
||||||
"'/*' probably isn't what you want here",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('fn', (validate_config_main, validate_manifest_main))
|
@pytest.mark.parametrize('fn', (validate_config_main, validate_manifest_main))
|
||||||
|
|
|
@ -5,6 +5,7 @@ import pytest
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
import pre_commit.constants as C
|
import pre_commit.constants as C
|
||||||
|
from pre_commit import envcontext
|
||||||
from pre_commit import git
|
from pre_commit import git
|
||||||
from pre_commit import util
|
from pre_commit import util
|
||||||
from pre_commit.commands.autoupdate import _check_hooks_still_exist_at_rev
|
from pre_commit.commands.autoupdate import _check_hooks_still_exist_at_rev
|
||||||
|
@ -176,6 +177,14 @@ def test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store):
|
||||||
assert cfg.read() == fmt.format(out_of_date.path, out_of_date.head_rev)
|
assert cfg.read() == fmt.format(out_of_date.path, out_of_date.head_rev)
|
||||||
|
|
||||||
|
|
||||||
|
def test_autoupdate_with_core_useBuiltinFSMonitor(out_of_date, tmpdir, store):
|
||||||
|
# force the setting on "globally" for git
|
||||||
|
home = tmpdir.join('fakehome').ensure_dir()
|
||||||
|
home.join('.gitconfig').write('[core]\nuseBuiltinFSMonitor = true\n')
|
||||||
|
with envcontext.envcontext((('HOME', str(home)),)):
|
||||||
|
test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store)
|
||||||
|
|
||||||
|
|
||||||
def test_autoupdate_pure_yaml(out_of_date, tmpdir, store):
|
def test_autoupdate_pure_yaml(out_of_date, tmpdir, store):
|
||||||
with mock.patch.object(util, 'Dumper', yaml.SafeDumper):
|
with mock.patch.object(util, 'Dumper', yaml.SafeDumper):
|
||||||
test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store)
|
test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store)
|
||||||
|
|
|
@ -278,11 +278,7 @@ def test_environment_not_sourced(tempdir_factory, store):
|
||||||
hook = os.path.join(path, '.git/hooks/pre-commit')
|
hook = os.path.join(path, '.git/hooks/pre-commit')
|
||||||
with open(hook) as f:
|
with open(hook) as f:
|
||||||
src = f.read()
|
src = f.read()
|
||||||
src = re.sub(
|
src = re.sub('\nINSTALL_PYTHON=.*\n', '\nINSTALL_PYTHON="/dne"\n', src)
|
||||||
'\nINSTALL_PYTHON =.*\n',
|
|
||||||
'\nINSTALL_PYTHON = "/dne"\n',
|
|
||||||
src,
|
|
||||||
)
|
|
||||||
with open(hook, 'w') as f:
|
with open(hook, 'w') as f:
|
||||||
f.write(src)
|
f.write(src)
|
||||||
|
|
||||||
|
|
|
@ -985,6 +985,18 @@ def test_fail_fast(cap_out, store, repo_with_failing_hook):
|
||||||
assert printed.count(b'Failing hook') == 1
|
assert printed.count(b'Failing hook') == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_fail_fast_per_hook(cap_out, store, repo_with_failing_hook):
|
||||||
|
with modify_config() as config:
|
||||||
|
# More than one hook
|
||||||
|
config['repos'][0]['hooks'] *= 2
|
||||||
|
config['repos'][0]['hooks'][0]['fail_fast'] = True
|
||||||
|
stage_a_file()
|
||||||
|
|
||||||
|
ret, printed = _do_run(cap_out, store, repo_with_failing_hook, run_opts())
|
||||||
|
# it should have only run one hook
|
||||||
|
assert printed.count(b'Failing hook') == 1
|
||||||
|
|
||||||
|
|
||||||
def test_classifier_removes_dne():
|
def test_classifier_removes_dne():
|
||||||
classifier = Classifier(('this_file_does_not_exist',))
|
classifier = Classifier(('this_file_does_not_exist',))
|
||||||
assert classifier.filenames == []
|
assert classifier.filenames == []
|
||||||
|
|
|
@ -227,6 +227,11 @@ def test_no_git_env():
|
||||||
'GIT_SSH': '/usr/bin/ssh',
|
'GIT_SSH': '/usr/bin/ssh',
|
||||||
'GIT_SSH_COMMAND': 'ssh -o',
|
'GIT_SSH_COMMAND': 'ssh -o',
|
||||||
'GIT_DIR': '/none/shall/pass',
|
'GIT_DIR': '/none/shall/pass',
|
||||||
|
'GIT_CONFIG_KEY_0': 'user.name',
|
||||||
|
'GIT_CONFIG_VALUE_0': 'anthony',
|
||||||
|
'GIT_CONFIG_KEY_1': 'user.email',
|
||||||
|
'GIT_CONFIG_VALUE_1': 'asottile@example.com',
|
||||||
|
'GIT_CONFIG_COUNT': '2',
|
||||||
}
|
}
|
||||||
no_git_env = git.no_git_env(env)
|
no_git_env = git.no_git_env(env)
|
||||||
assert no_git_env == {
|
assert no_git_env == {
|
||||||
|
@ -234,6 +239,11 @@ def test_no_git_env():
|
||||||
'GIT_EXEC_PATH': '/some/git/exec/path',
|
'GIT_EXEC_PATH': '/some/git/exec/path',
|
||||||
'GIT_SSH': '/usr/bin/ssh',
|
'GIT_SSH': '/usr/bin/ssh',
|
||||||
'GIT_SSH_COMMAND': 'ssh -o',
|
'GIT_SSH_COMMAND': 'ssh -o',
|
||||||
|
'GIT_CONFIG_KEY_0': 'user.name',
|
||||||
|
'GIT_CONFIG_VALUE_0': 'anthony',
|
||||||
|
'GIT_CONFIG_KEY_1': 'user.email',
|
||||||
|
'GIT_CONFIG_VALUE_1': 'asottile@example.com',
|
||||||
|
'GIT_CONFIG_COUNT': '2',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -111,8 +111,8 @@ def test_local_conda_additional_dependencies(store):
|
||||||
'name': 'local-conda',
|
'name': 'local-conda',
|
||||||
'entry': 'python',
|
'entry': 'python',
|
||||||
'language': 'conda',
|
'language': 'conda',
|
||||||
'args': ['-c', 'import tzdata; print("OK")'],
|
'args': ['-c', 'import botocore; print("OK")'],
|
||||||
'additional_dependencies': ['python-tzdata'],
|
'additional_dependencies': ['botocore'],
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
hook = _get_hook(config, store, 'local-conda')
|
hook = _get_hook(config, store, 'local-conda')
|
||||||
|
@ -164,7 +164,7 @@ def test_python_hook_weird_setup_cfg(in_git_dir, tempdir_factory, store):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_python_venv(tempdir_factory, store): # pragma: no cover (no venv)
|
def test_python_venv(tempdir_factory, store):
|
||||||
_test_hook_repo(
|
_test_hook_repo(
|
||||||
tempdir_factory, store, 'python_venv_hooks_repo',
|
tempdir_factory, store, 'python_venv_hooks_repo',
|
||||||
'foo', [os.devnull],
|
'foo', [os.devnull],
|
||||||
|
@ -245,7 +245,6 @@ def test_run_a_docker_image_hook(tempdir_factory, store, hook_id):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@xfailif_windows # pragma: win32 no cover
|
|
||||||
def test_run_a_node_hook(tempdir_factory, store):
|
def test_run_a_node_hook(tempdir_factory, store):
|
||||||
_test_hook_repo(
|
_test_hook_repo(
|
||||||
tempdir_factory, store, 'node_hooks_repo',
|
tempdir_factory, store, 'node_hooks_repo',
|
||||||
|
@ -253,7 +252,6 @@ def test_run_a_node_hook(tempdir_factory, store):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@xfailif_windows # pragma: win32 no cover
|
|
||||||
def test_run_a_node_hook_default_version(tempdir_factory, store):
|
def test_run_a_node_hook_default_version(tempdir_factory, store):
|
||||||
# make sure that this continues to work for platforms where node is not
|
# make sure that this continues to work for platforms where node is not
|
||||||
# installed at the system
|
# installed at the system
|
||||||
|
@ -263,7 +261,6 @@ def test_run_a_node_hook_default_version(tempdir_factory, store):
|
||||||
test_run_a_node_hook(tempdir_factory, store)
|
test_run_a_node_hook(tempdir_factory, store)
|
||||||
|
|
||||||
|
|
||||||
@xfailif_windows # pragma: win32 no cover
|
|
||||||
def test_run_versioned_node_hook(tempdir_factory, store):
|
def test_run_versioned_node_hook(tempdir_factory, store):
|
||||||
_test_hook_repo(
|
_test_hook_repo(
|
||||||
tempdir_factory, store, 'node_versioned_hooks_repo',
|
tempdir_factory, store, 'node_versioned_hooks_repo',
|
||||||
|
@ -271,7 +268,6 @@ def test_run_versioned_node_hook(tempdir_factory, store):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@xfailif_windows # pragma: win32 no cover
|
|
||||||
def test_node_hook_with_npm_userconfig_set(tempdir_factory, store, tmpdir):
|
def test_node_hook_with_npm_userconfig_set(tempdir_factory, store, tmpdir):
|
||||||
cfg = tmpdir.join('cfg')
|
cfg = tmpdir.join('cfg')
|
||||||
cfg.write('cache=/dne\n')
|
cfg.write('cache=/dne\n')
|
||||||
|
@ -653,7 +649,6 @@ def test_additional_ruby_dependencies_installed(tempdir_factory, store):
|
||||||
assert 'tins' in output
|
assert 'tins' in output
|
||||||
|
|
||||||
|
|
||||||
@xfailif_windows # pragma: win32 no cover
|
|
||||||
def test_additional_node_dependencies_installed(tempdir_factory, store):
|
def test_additional_node_dependencies_installed(tempdir_factory, store):
|
||||||
path = make_repo(tempdir_factory, 'node_hooks_repo')
|
path = make_repo(tempdir_factory, 'node_hooks_repo')
|
||||||
config = make_config_from_repo(path)
|
config = make_config_from_repo(path)
|
||||||
|
@ -1007,6 +1002,7 @@ def test_manifest_hooks(tempdir_factory, store):
|
||||||
types=['file'],
|
types=['file'],
|
||||||
types_or=[],
|
types_or=[],
|
||||||
verbose=False,
|
verbose=False,
|
||||||
|
fail_fast=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1025,13 +1021,13 @@ def test_local_perl_additional_dependencies(store):
|
||||||
'name': 'hello',
|
'name': 'hello',
|
||||||
'entry': 'perltidy --version',
|
'entry': 'perltidy --version',
|
||||||
'language': 'perl',
|
'language': 'perl',
|
||||||
'additional_dependencies': ['SHANCOCK/Perl-Tidy-20200110.tar.gz'],
|
'additional_dependencies': ['SHANCOCK/Perl-Tidy-20211029.tar.gz'],
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
hook = _get_hook(config, store, 'hello')
|
hook = _get_hook(config, store, 'hello')
|
||||||
ret, out = _hook_run(hook, (), color=False)
|
ret, out = _hook_run(hook, (), color=False)
|
||||||
assert ret == 0
|
assert ret == 0
|
||||||
assert _norm_out(out).startswith(b'This is perltidy, v20200110')
|
assert _norm_out(out).startswith(b'This is perltidy, v20211029')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -181,9 +181,11 @@ def test_img_conflict(img_staged, patch_dir):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def submodule_with_commits(tempdir_factory):
|
def repo_with_commits(tempdir_factory):
|
||||||
path = git_dir(tempdir_factory)
|
path = git_dir(tempdir_factory)
|
||||||
with cwd(path):
|
with cwd(path):
|
||||||
|
open('foo', 'a+').close()
|
||||||
|
cmd_output('git', 'add', 'foo')
|
||||||
git_commit()
|
git_commit()
|
||||||
rev1 = cmd_output('git', 'rev-parse', 'HEAD')[1].strip()
|
rev1 = cmd_output('git', 'rev-parse', 'HEAD')[1].strip()
|
||||||
git_commit()
|
git_commit()
|
||||||
|
@ -196,18 +198,21 @@ def checkout_submodule(rev):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sub_staged(submodule_with_commits, tempdir_factory):
|
def sub_staged(repo_with_commits, tempdir_factory):
|
||||||
path = git_dir(tempdir_factory)
|
path = git_dir(tempdir_factory)
|
||||||
with cwd(path):
|
with cwd(path):
|
||||||
|
open('bar', 'a+').close()
|
||||||
|
cmd_output('git', 'add', 'bar')
|
||||||
|
git_commit()
|
||||||
cmd_output(
|
cmd_output(
|
||||||
'git', 'submodule', 'add', submodule_with_commits.path, 'sub',
|
'git', 'submodule', 'add', repo_with_commits.path, 'sub',
|
||||||
)
|
)
|
||||||
checkout_submodule(submodule_with_commits.rev1)
|
checkout_submodule(repo_with_commits.rev1)
|
||||||
cmd_output('git', 'add', 'sub')
|
cmd_output('git', 'add', 'sub')
|
||||||
yield auto_namedtuple(
|
yield auto_namedtuple(
|
||||||
path=path,
|
path=path,
|
||||||
sub_path=os.path.join(path, 'sub'),
|
sub_path=os.path.join(path, 'sub'),
|
||||||
submodule=submodule_with_commits,
|
submodule=repo_with_commits,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -242,6 +247,34 @@ def test_sub_something_unstaged(sub_staged, patch_dir):
|
||||||
_test_sub_state(sub_staged, 'rev2', 'AM')
|
_test_sub_state(sub_staged, 'rev2', 'AM')
|
||||||
|
|
||||||
|
|
||||||
|
def test_submodule_does_not_discard_changes(sub_staged, patch_dir):
|
||||||
|
with open('bar', 'w') as f:
|
||||||
|
f.write('unstaged changes')
|
||||||
|
|
||||||
|
foo_path = os.path.join(sub_staged.sub_path, 'foo')
|
||||||
|
with open(foo_path, 'w') as f:
|
||||||
|
f.write('foo contents')
|
||||||
|
|
||||||
|
with staged_files_only(patch_dir):
|
||||||
|
with open('bar') as f:
|
||||||
|
assert f.read() == ''
|
||||||
|
|
||||||
|
with open(foo_path) as f:
|
||||||
|
assert f.read() == 'foo contents'
|
||||||
|
|
||||||
|
with open('bar') as f:
|
||||||
|
assert f.read() == 'unstaged changes'
|
||||||
|
|
||||||
|
with open(foo_path) as f:
|
||||||
|
assert f.read() == 'foo contents'
|
||||||
|
|
||||||
|
|
||||||
|
def test_submodule_does_not_discard_changes_recurse(sub_staged, patch_dir):
|
||||||
|
cmd_output('git', 'config', 'submodule.recurse', '1', cwd=sub_staged.path)
|
||||||
|
|
||||||
|
test_submodule_does_not_discard_changes(sub_staged, patch_dir)
|
||||||
|
|
||||||
|
|
||||||
def test_stage_utf8_changes(foo_staged, patch_dir):
|
def test_stage_utf8_changes(foo_staged, patch_dir):
|
||||||
contents = '\u2603'
|
contents = '\u2603'
|
||||||
with open('foo', 'w', encoding='UTF-8') as foo_file:
|
with open('foo', 'w', encoding='UTF-8') as foo_file:
|
||||||
|
|
Loading…
Add table
Reference in a new issue