mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-02-17 16:24:40 +04:00
Compare commits
486 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8416413a0e | ||
|
|
37a879e65e | ||
|
|
8a0630ca1a | ||
|
|
fcbc745744 | ||
|
|
51592eecec | ||
|
|
67e8faf80b | ||
|
|
c251e6b6d0 | ||
|
|
98ccafa3ce | ||
|
|
48953556d0 | ||
|
|
2cedd58e69 | ||
|
|
465192d7de | ||
|
|
fd42f96874 | ||
|
|
8ea2b790d8 | ||
|
|
1af6c8fa95 | ||
|
|
3358a3b540 | ||
|
|
bdf68790b7 | ||
|
|
e436690f14 | ||
|
|
8d34f95308 | ||
|
|
9c7ea88ab9 | ||
|
|
844dacc168 | ||
|
|
6a1d543e52 | ||
|
|
66278a9a0b | ||
|
|
1b32c50bc7 | ||
|
|
063229aee7 | ||
|
|
49e28eea48 | ||
|
|
d5c273a2ba | ||
|
|
17cf886473 | ||
|
|
cb63a5cb9a | ||
|
|
f80801d75a | ||
|
|
9143fc3545 | ||
|
|
725acc969a | ||
|
|
3815e2e6d8 | ||
|
|
aa2961c122 | ||
|
|
46297f7cd6 | ||
|
|
95eec75004 | ||
|
|
5e4b3546f3 | ||
|
|
8bbfcf1f82 | ||
|
|
65175f3cf3 | ||
|
|
fc33a62f3c | ||
|
|
2db924eb98 | ||
|
|
ddfcf4034b | ||
|
|
1b424ccfa2 | ||
|
|
221637b0cb | ||
|
|
7ad23528d0 | ||
|
|
f415f6c4d7 | ||
|
|
99fa9ba5ef | ||
|
|
ad0d4cd427 | ||
|
|
924680e974 | ||
|
|
2930ea0fcd | ||
|
|
b96127c485 | ||
|
|
954cc3b3b3 | ||
|
|
e671830402 | ||
|
|
c78f248c60 | ||
|
|
e70b313c80 | ||
|
|
87a681f866 | ||
|
|
b74a22d96c | ||
|
|
cc899de192 | ||
|
|
2a0bcea757 | ||
|
|
f1cc7a445f | ||
|
|
72a3b71f0e | ||
|
|
c8925a457a | ||
|
|
a5fe6c500c | ||
|
|
6f1f433a9c | ||
|
|
c6817210b1 | ||
|
|
4fd4537bc6 | ||
|
|
a1d7bed86f | ||
|
|
d1d5b3d564 | ||
|
|
9c228a0bd8 | ||
|
|
d4f0c6e8a7 | ||
|
|
5f0c773e74 | ||
|
|
43b426a501 | ||
|
|
8a4af027a1 | ||
|
|
466f6c4a39 | ||
|
|
d2b61d0ef2 | ||
|
|
43592c2a29 | ||
|
|
6d47b8d52b | ||
|
|
aa48766b88 | ||
|
|
bf6f11dc6c | ||
|
|
3e8d0f5e1c | ||
|
|
ff7256cedf | ||
|
|
b7eb412c79 | ||
|
|
7b88c63ae6 | ||
|
|
94b97e28f7 | ||
|
|
2f93b80484 | ||
|
|
4f90a1e88a | ||
|
|
aba1ce04e7 | ||
|
|
e2210c97e2 | ||
|
|
804c853d8f | ||
|
|
edd0002e43 | ||
|
|
b152e922ef | ||
|
|
c3125a4d36 | ||
|
|
c2c061cf63 | ||
|
|
cd429db5e2 | ||
|
|
9b9f8e254d | ||
|
|
86300a4a7e | ||
|
|
77edad8455 | ||
|
|
18b393905e | ||
|
|
31cb945ffb | ||
|
|
28c3d81bd2 | ||
|
|
aa85be9340 | ||
|
|
1027596280 | ||
|
|
db85eeed2d | ||
|
|
cb14bc2d9c | ||
|
|
109628c505 | ||
|
|
74233a125a | ||
|
|
85783bdc0b | ||
|
|
9da45a686a | ||
|
|
708ca3b581 | ||
|
|
611195a088 | ||
|
|
0de4c8028a | ||
|
|
46de4da34e | ||
|
|
cc4a522415 | ||
|
|
772d7d45d3 | ||
|
|
222c62bc5d | ||
|
|
3d5548b487 | ||
|
|
4235a877f3 | ||
|
|
dbccd57db0 | ||
|
|
d07e52901c | ||
|
|
801b956304 | ||
|
|
a2f7b80e89 | ||
|
|
d31722386e | ||
|
|
7555e11098 | ||
|
|
05e365fe08 | ||
|
|
1d2f1c0cce | ||
|
|
33e020f315 | ||
|
|
e7cfc0d2cb | ||
|
|
7441a62eb1 | ||
|
|
eec11bd124 | ||
|
|
fa08d1d637 | ||
|
|
6c068a78d6 | ||
|
|
c9454e2ec3 | ||
|
|
e687548842 | ||
|
|
a4e4cef335 | ||
|
|
de8590064e | ||
|
|
5679399d90 | ||
|
|
a7b671a758 | ||
|
|
364e6d77f0 | ||
|
|
504149d2ca | ||
|
|
c2c68d985c | ||
|
|
0f8f383d53 | ||
|
|
d5c21926ab | ||
|
|
8a3ee454a2 | ||
|
|
917e2102be | ||
|
|
9d4ab670d1 | ||
|
|
d46423ffe1 | ||
|
|
8133abd730 | ||
|
|
da0c1d0cfa | ||
|
|
f641f6a157 | ||
|
|
a68a19d217 | ||
|
|
88317ddb34 | ||
|
|
faa6f8c70c | ||
|
|
f632459bc6 | ||
|
|
0252908c27 | ||
|
|
69b5dce12a | ||
|
|
d56502acab | ||
|
|
49a9664cd0 | ||
|
|
60db5d78d1 | ||
|
|
9dd247898c | ||
|
|
15d9f7f61e | ||
|
|
1f128556e4 | ||
|
|
dd144c95f6 | ||
|
|
5526bb2137 | ||
|
|
9ee0768353 | ||
|
|
eeac061b31 | ||
|
|
296f59266e | ||
|
|
16023286d2 | ||
|
|
0142f45322 | ||
|
|
d7e21cd29c | ||
|
|
5c3d006443 | ||
|
|
0d4c6da36e | ||
|
|
85fe18253f | ||
|
|
74d05b444d | ||
|
|
a9f19f4cc0 | ||
|
|
4e121ef25c | ||
|
|
7b4667e9e6 | ||
|
|
d46c8fc051 | ||
|
|
fc622159a6 | ||
|
|
716da1e49c | ||
|
|
0939c11b4f | ||
|
|
3bdf9fb91b | ||
|
|
75b3e52e57 | ||
|
|
5e11c266ae | ||
|
|
e58009684c | ||
|
|
7b868c3508 | ||
|
|
a768c038e3 | ||
|
|
e525726855 | ||
|
|
3187538d2b | ||
|
|
61d9c95cc1 | ||
|
|
15bd0c7993 | ||
|
|
92678c3fa2 | ||
|
|
032d8e2704 | ||
|
|
73848383f2 | ||
|
|
96e0712f43 | ||
|
|
3388e2dbdf | ||
|
|
10f8853631 | ||
|
|
9682f93e31 | ||
|
|
5f8ebaefa9 | ||
|
|
9cce283422 | ||
|
|
9c9983dba0 | ||
|
|
7dc0a59ee5 | ||
|
|
d3fa7f415c | ||
|
|
08478ec176 | ||
|
|
047439abff | ||
|
|
23a2b7360e | ||
|
|
cffabe54be | ||
|
|
51df34e5fb | ||
|
|
e36cefc8bd | ||
|
|
2280645d0e | ||
|
|
1d474994e0 | ||
|
|
14169eb31d | ||
|
|
75f2710bd4 | ||
|
|
762e68173b | ||
|
|
7f15dc75ee | ||
|
|
5f4ed54cfe | ||
|
|
c69e32e925 | ||
|
|
48f0dc9615 | ||
|
|
44b625ebd3 | ||
|
|
61cc55a59c | ||
|
|
c9945b9aa3 | ||
|
|
d988767b41 | ||
|
|
0d8b2451ca | ||
|
|
155c521348 | ||
|
|
676e51aa5e | ||
|
|
997ea0ad52 | ||
|
|
19aa121db0 | ||
|
|
a4ab977cc3 | ||
|
|
3f3760b86c | ||
|
|
051f4e2550 | ||
|
|
c68c6b944a | ||
|
|
5e05b01215 | ||
|
|
84f91646bb | ||
|
|
d33801e781 | ||
|
|
f56b75dd77 | ||
|
|
5d692d7e06 | ||
|
|
0845e4e816 | ||
|
|
5f4b828999 | ||
|
|
9ac229dad8 | ||
|
|
493c20ce91 | ||
|
|
e2c6a822c7 | ||
|
|
818240e425 | ||
|
|
fe9ba6b53f | ||
|
|
ac42dc586a | ||
|
|
ea8244b229 | ||
|
|
9ebda91889 | ||
|
|
3dd1875df8 | ||
|
|
bde292b510 | ||
|
|
a4ae868633 | ||
|
|
0c3d605fa0 | ||
|
|
5a4b5b1f8e | ||
|
|
a1f1d19156 | ||
|
|
93b1a14402 | ||
|
|
23df082edc | ||
|
|
1803db979f | ||
|
|
b2ff5e401b | ||
|
|
8c75a26f2d | ||
|
|
13b673ce3c | ||
|
|
3557077bbc | ||
|
|
2bffc0ad85 | ||
|
|
60273ca81e | ||
|
|
f8488e36c8 | ||
|
|
d537c09032 | ||
|
|
9660d3b451 | ||
|
|
5e4af63e85 | ||
|
|
4f73b8a2af | ||
|
|
9bf6856db3 | ||
|
|
8bf22bd3c1 | ||
|
|
1c439b5a79 | ||
|
|
3126802bf4 | ||
|
|
e72699b9ef | ||
|
|
8891089cf5 | ||
|
|
854f698531 | ||
|
|
c25ea47cf7 | ||
|
|
f94744a699 | ||
|
|
5da4258b17 | ||
|
|
e891f8606e | ||
|
|
50b1511a5b | ||
|
|
9a7ed8be09 | ||
|
|
f073f8e13c | ||
|
|
1fc28903ab | ||
|
|
c7f472d176 | ||
|
|
5d273951e0 | ||
|
|
f88cc61256 | ||
|
|
c716de12f7 | ||
|
|
f4a2d52bb4 | ||
|
|
8034430539 | ||
|
|
18348f5d0d | ||
|
|
a0a734750e | ||
|
|
bcd8274e1c | ||
|
|
cd09c3525e | ||
|
|
c389ac0ba8 | ||
|
|
64985bd63d | ||
|
|
f5a98eb152 | ||
|
|
08b670ff9e | ||
|
|
72b7b53ae3 | ||
|
|
9c2a01186b | ||
|
|
ee49d4289f | ||
|
|
926071b6a7 | ||
|
|
0deb445a73 | ||
|
|
8923fa368a | ||
|
|
cf3e826070 | ||
|
|
1dd85c904e | ||
|
|
51104fa94a | ||
|
|
0fd2501503 | ||
|
|
420a15f87e | ||
|
|
4c0623963f | ||
|
|
5df9eb9d66 | ||
|
|
ddbee32ad0 | ||
|
|
bab5f70a38 | ||
|
|
4f045cbc21 | ||
|
|
27d77fc8bc | ||
|
|
e885f2e76e | ||
|
|
148df0d718 | ||
|
|
4727922b93 | ||
|
|
5cd99d81ad | ||
|
|
8656797c78 | ||
|
|
6f941298a4 | ||
|
|
cfcb88364e | ||
|
|
6896025288 | ||
|
|
f5a716f1b1 | ||
|
|
5027592625 | ||
|
|
597bf7caf8 | ||
|
|
84f040f58a | ||
|
|
bb49560dc9 | ||
|
|
0477abd3ce | ||
|
|
ee71a9345c | ||
|
|
df2cada973 | ||
|
|
fdb9ede460 | ||
|
|
a412e5492d | ||
|
|
d5f919e72f | ||
|
|
7a7772fcda | ||
|
|
4caea677c6 | ||
|
|
d3c0a66d23 | ||
|
|
950451e1ef | ||
|
|
f39154f69f | ||
|
|
f2661bfc31 | ||
|
|
e3e17a1617 | ||
|
|
02e9680a46 | ||
|
|
8ab9747b33 | ||
|
|
321a90e0a1 | ||
|
|
63a180a935 | ||
|
|
fe73f519c7 | ||
|
|
0616c0abf7 | ||
|
|
321685ee0e | ||
|
|
5ce4a549d3 | ||
|
|
2822de9aa6 | ||
|
|
7f386a752e | ||
|
|
2700a7d622 | ||
|
|
294590fd12 | ||
|
|
7bfa7e15c6 | ||
|
|
a631abdabf | ||
|
|
cdd360645a | ||
|
|
4ded56efac | ||
|
|
192be6079b | ||
|
|
cddc9cff0f | ||
|
|
8ba9bc6d89 | ||
|
|
08fa5ffc43 | ||
|
|
9655158d93 | ||
|
|
1054afd978 | ||
|
|
d23990cc8b | ||
|
|
51b14c2b37 | ||
|
|
4666b6956f | ||
|
|
8d84a7a270 | ||
|
|
ccbf15150f | ||
|
|
25b8ad7528 | ||
|
|
0cc2856883 | ||
|
|
4f6ba18cad | ||
|
|
c3402e6648 | ||
|
|
c3613b954a | ||
|
|
d3883ce7f7 | ||
|
|
5bc56889e9 | ||
|
|
a2373d0a81 | ||
|
|
f5ec578647 | ||
|
|
8db5aaf4f3 | ||
|
|
8afe5958e6 | ||
|
|
ea2569027b | ||
|
|
8f2dfce5d1 | ||
|
|
4fdfb25a52 | ||
|
|
4bd1677cda | ||
|
|
16869444ca | ||
|
|
5635079373 | ||
|
|
c5fc8627be | ||
|
|
abbfb2e9b9 | ||
|
|
4d970de329 | ||
|
|
915b930a5d | ||
|
|
97c7870f89 | ||
|
|
6804100701 | ||
|
|
f7df13f3d8 | ||
|
|
0afb95ccca | ||
|
|
b609368ca5 | ||
|
|
0c1267b214 | ||
|
|
0359fae2da | ||
|
|
cc7bf965eb | ||
|
|
7783a3e63a | ||
|
|
e846829992 | ||
|
|
bfe1a72734 | ||
|
|
1129e7d222 | ||
|
|
7260d24d0f | ||
|
|
9868b1a347 | ||
|
|
d216cdd5c1 | ||
|
|
c2e432cdf6 | ||
|
|
909dd0e8a1 | ||
|
|
3c2ca11332 | ||
|
|
2530913fad | ||
|
|
650afe4ffa | ||
|
|
f54386203e | ||
|
|
50cf02764c | ||
|
|
5b50acbd2c | ||
|
|
77b4ea38ca | ||
|
|
6abb05a60c | ||
|
|
2adca78c6f | ||
|
|
2e1cfa8f05 | ||
|
|
420902f67c | ||
|
|
6eacdd440e | ||
|
|
6e8051b9e6 | ||
|
|
840cf532a9 | ||
|
|
f4bd44996c | ||
|
|
dd8e717ed6 | ||
|
|
6d3a7eeef5 | ||
|
|
83e05e607e | ||
|
|
6b88fe577c | ||
|
|
ea88b377d5 | ||
|
|
bff5e0e738 | ||
|
|
2c39545d24 | ||
|
|
14c38d18fc | ||
|
|
50848aaca2 | ||
|
|
a0fc6022d7 | ||
|
|
f042540311 | ||
|
|
7512e3b7e1 | ||
|
|
6014ebd2e7 | ||
|
|
043565d28a | ||
|
|
2bd853e96f | ||
|
|
d24055cb40 | ||
|
|
9a56f8dca0 | ||
|
|
f5a365c2c8 | ||
|
|
966c67a832 | ||
|
|
c36f03cd2e | ||
|
|
a26b631aae | ||
|
|
f1b5f66374 | ||
|
|
0316676fdc | ||
|
|
70bfd76ced | ||
|
|
59ed51a309 | ||
|
|
628c876b2d | ||
|
|
48ae18a2cb | ||
|
|
ae34a962d7 | ||
|
|
bcf0230772 | ||
|
|
37685a7f42 | ||
|
|
87ab767175 | ||
|
|
9afd63948e | ||
|
|
ceb429b253 | ||
|
|
619f2bf5a9 | ||
|
|
dc667ab9fb | ||
|
|
bf1a1fa5fd | ||
|
|
0dbc154380 | ||
|
|
83e64e2071 | ||
|
|
e1567b6148 | ||
|
|
60a42e9419 | ||
|
|
8529a0c1d3 | ||
|
|
990643c1e0 | ||
|
|
978e26c544 | ||
|
|
0920cb33ee | ||
|
|
c787efd558 | ||
|
|
05c8911363 | ||
|
|
10f835c501 | ||
|
|
d05b7888ab | ||
|
|
0224be8194 | ||
|
|
3d09b66c6b | ||
|
|
f0baffb01f | ||
|
|
8e57e8075d | ||
|
|
017fa5c0b8 | ||
|
|
0cec5bd6f0 | ||
|
|
5425c754a0 | ||
|
|
0a0754e44a | ||
|
|
092e9a50ae | ||
|
|
cddaa0dddc | ||
|
|
21407882fe | ||
|
|
887c5e1142 | ||
|
|
848a73ed40 | ||
|
|
4a50859936 | ||
|
|
d33df92add | ||
|
|
def3fa3929 | ||
|
|
12a979ea75 | ||
|
|
40e69ce8e3 | ||
|
|
7daceb083b | ||
|
|
ff3150d58a | ||
|
|
0024484f5b | ||
|
|
524a236072 |
214 changed files with 6349 additions and 4408 deletions
|
|
@ -16,6 +16,12 @@ body:
|
|||
placeholder: ...
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
95% of issues created are duplicates.
|
||||
please try extra hard to find them first.
|
||||
it's very unlikely your problem is unique.
|
||||
- type: textarea
|
||||
id: freeform
|
||||
attributes:
|
||||
38
.github/ISSUE_TEMPLATE/01_feature.yaml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/01_feature.yaml
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
name: feature request
|
||||
description: something new
|
||||
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: input
|
||||
id: search
|
||||
attributes:
|
||||
label: search you tried in the issue tracker
|
||||
placeholder: ...
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
95% of issues created are duplicates.
|
||||
please try extra hard to find them first.
|
||||
it's very unlikely your feature idea is a new one.
|
||||
- type: textarea
|
||||
id: freeform
|
||||
attributes:
|
||||
label: describe your actual problem
|
||||
placeholder: 'I want to do ... I tried ... It does not work because ...'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: pre-commit --version
|
||||
placeholder: pre-commit x.x.x
|
||||
validations:
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: documentation
|
||||
url: https://pre-commit.com
|
||||
about: please check the docs first
|
||||
- name: pre-commit.ci issues
|
||||
url: https://github.com/pre-commit-ci/issues
|
||||
about: please report issues about pre-commit.ci here
|
||||
9
.github/actions/pre-test/action.yml
vendored
Normal file
9
.github/actions/pre-test/action.yml
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
inputs:
|
||||
env:
|
||||
default: ${{ matrix.env }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: asottile/workflows/.github/actions/latest-git@v1.4.0
|
||||
if: inputs.env == 'py39' && runner.os == 'Linux'
|
||||
84
.github/workflows/languages.yaml
vendored
Normal file
84
.github/workflows/languages.yaml
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
name: languages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, test-me-*]
|
||||
tags: '*'
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
vars:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
languages: ${{ steps.vars.outputs.languages }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: install deps
|
||||
run: python -mpip install -e . -r requirements-dev.txt
|
||||
- name: vars
|
||||
run: testing/languages ${{ github.event_name == 'push' && '--all' || '' }}
|
||||
id: vars
|
||||
language:
|
||||
needs: [vars]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: needs.vars.outputs.languages != '[]'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJSON(needs.vars.outputs.languages) }}
|
||||
steps:
|
||||
- uses: asottile/workflows/.github/actions/fast-checkout@v1.8.1
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- run: echo "$CONDA\Scripts" >> "$GITHUB_PATH"
|
||||
shell: bash
|
||||
if: matrix.os == 'windows-latest' && matrix.language == 'conda'
|
||||
- run: testing/get-coursier.sh
|
||||
shell: bash
|
||||
if: matrix.language == 'coursier'
|
||||
- run: testing/get-dart.sh
|
||||
shell: bash
|
||||
if: matrix.language == 'dart'
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
lua5.3 \
|
||||
liblua5.3-dev \
|
||||
luarocks
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.language == 'lua'
|
||||
- run: |
|
||||
echo 'C:\Strawberry\perl\bin' >> "$GITHUB_PATH"
|
||||
echo 'C:\Strawberry\perl\site\bin' >> "$GITHUB_PATH"
|
||||
echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH"
|
||||
shell: bash
|
||||
if: matrix.os == 'windows-latest' && matrix.language == 'perl'
|
||||
- uses: haskell/actions/setup@v2
|
||||
if: matrix.language == 'haskell'
|
||||
- uses: r-lib/actions/setup-r@v2
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.language == 'r'
|
||||
|
||||
- name: install deps
|
||||
run: python -mpip install -e . -r requirements-dev.txt
|
||||
- name: run tests
|
||||
run: coverage run -m pytest tests/languages/${{ matrix.language }}_test.py
|
||||
- name: check coverage
|
||||
run: coverage report --include pre_commit/languages/${{ matrix.language }}.py,tests/languages/${{ matrix.language }}_test.py
|
||||
collector:
|
||||
needs: [language]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: check for failures
|
||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
run: echo job failed && exit 1
|
||||
23
.github/workflows/main.yml
vendored
Normal file
23
.github/workflows/main.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, test-me-*]
|
||||
tags: '*'
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
main-windows:
|
||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
||||
with:
|
||||
env: '["py310"]'
|
||||
os: windows-latest
|
||||
main-linux:
|
||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
||||
with:
|
||||
env: '["py310", "py311", "py312", "py313"]'
|
||||
os: ubuntu-latest
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
|
@ -10,36 +10,35 @@ repos:
|
|||
- id: name-tests-test
|
||||
- id: requirements-txt-fixer
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.2.0
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v3.9.0
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.16.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/)
|
||||
args: [--py37-plus, --add-import, 'from __future__ import annotations']
|
||||
exclude: ^pre_commit/resources/
|
||||
args: [--py310-plus, --add-import, 'from __future__ import annotations']
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v2.4.0
|
||||
rev: v4.0.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
args: [--py36-plus]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
rev: v3.21.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
- repo: https://github.com/pre-commit/mirrors-autopep8
|
||||
rev: v2.0.1
|
||||
args: [--py310-plus]
|
||||
- repo: https://github.com/hhatto/autopep8
|
||||
rev: v2.3.2
|
||||
hooks:
|
||||
- id: autopep8
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
rev: 7.3.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.991
|
||||
rev: v1.19.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-all]
|
||||
additional_dependencies: [types-pyyaml]
|
||||
exclude: ^testing/resources/
|
||||
|
|
|
|||
379
CHANGELOG.md
379
CHANGELOG.md
|
|
@ -1,3 +1,382 @@
|
|||
4.5.1 - 2025-12-16
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix `language: python` with `repo: local` without `additional_dependencies`.
|
||||
- #3597 PR by @asottile.
|
||||
|
||||
4.5.0 - 2025-11-22
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Add `pre-commit hazmat`.
|
||||
- #3585 PR by @asottile.
|
||||
|
||||
4.4.0 - 2025-11-08
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Add `--fail-fast` option to `pre-commit run`.
|
||||
- #3528 PR by @JulianMaurin.
|
||||
- Upgrade `ruby-build` / `rbenv`.
|
||||
- #3566 PR by @asottile.
|
||||
- #3565 issue by @MRigal.
|
||||
- Add `language: unsupported` / `language: unsupported_script` as aliases
|
||||
for `language: system` / `language: script` (which will eventually be
|
||||
deprecated).
|
||||
- #3577 PR by @asottile.
|
||||
- Add support docker-in-docker detection for cgroups v2.
|
||||
- #3535 PR by @br-rhrbacek.
|
||||
- #3360 issue by @JasonAlt.
|
||||
|
||||
### Fixes
|
||||
- Handle when docker gives `SecurityOptions: null`.
|
||||
- #3537 PR by @asottile.
|
||||
- #3514 issue by @jenstroeger.
|
||||
- Fix error context for invalid `stages` in `.pre-commit-config.yaml`.
|
||||
- #3576 PR by @asottile.
|
||||
|
||||
4.3.0 - 2025-08-09
|
||||
==================
|
||||
|
||||
### Features
|
||||
- `language: docker` / `language: docker_image`: detect rootless docker.
|
||||
- #3446 PR by @matthewhughes934.
|
||||
- #1243 issue by @dkolepp.
|
||||
- `language: julia`: avoid `startup.jl` when executing hooks.
|
||||
- #3496 PR by @ericphanson.
|
||||
- `language: dart`: support latest dart versions which require a higher sdk
|
||||
lower bound.
|
||||
- #3507 PR by @bc-lee.
|
||||
|
||||
4.2.0 - 2025-03-18
|
||||
==================
|
||||
|
||||
### Features
|
||||
- For `language: python` first attempt a versioned python executable for
|
||||
the default language version before consulting a potentially unversioned
|
||||
`sys.executable`.
|
||||
- #3430 PR by @asottile.
|
||||
|
||||
### Fixes
|
||||
- Handle error during conflict detection when a file is named "HEAD"
|
||||
- #3425 PR by @tusharsadhwani.
|
||||
|
||||
4.1.0 - 2025-01-20
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Add `language: julia`.
|
||||
- #3348 PR by @fredrikekre.
|
||||
- #2689 issue @jmuchovej.
|
||||
|
||||
### Fixes
|
||||
- Disable automatic toolchain switching for `language: golang`.
|
||||
- #3304 PR by @AleksaC.
|
||||
- #3300 issue by @AleksaC.
|
||||
- #3149 issue by @nijel.
|
||||
- Fix `language: r` installation when initiated by RStudio.
|
||||
- #3389 PR by @lorenzwalthert.
|
||||
- #3385 issue by @lorenzwalthert.
|
||||
|
||||
|
||||
4.0.1 - 2024-10-08
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix `pre-commit migrate-config` for unquoted deprecated stages names with
|
||||
purelib `pyyaml`.
|
||||
- #3324 PR by @asottile.
|
||||
- pre-commit-ci/issues#234 issue by @lorenzwalthert.
|
||||
|
||||
4.0.0 - 2024-10-05
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Improve `pre-commit migrate-config` to handle more yaml formats.
|
||||
- #3301 PR by @asottile.
|
||||
- Handle `stages` deprecation in `pre-commit migrate-config`.
|
||||
- #3302 PR by @asottile.
|
||||
- #2732 issue by @asottile.
|
||||
- Upgrade `ruby-build`.
|
||||
- #3199 PR by @ThisGuyCodes.
|
||||
- Add "sensible regex" warnings to `repo: meta`.
|
||||
- #3311 PR by @asottile.
|
||||
- Add warnings for deprecated `stages` (`commit` -> `pre-commit`, `push` ->
|
||||
`pre-push`, `merge-commit` -> `pre-merge-commit`).
|
||||
- #3312 PR by @asottile.
|
||||
- #3313 PR by @asottile.
|
||||
- #3315 PR by @asottile.
|
||||
- #2732 issue by @asottile.
|
||||
|
||||
### Updating
|
||||
- `language: python_venv` has been removed -- use `language: python` instead.
|
||||
- #3320 PR by @asottile.
|
||||
- #2734 issue by @asottile.
|
||||
|
||||
3.8.0 - 2024-07-28
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Implement health checks for `language: r` so environments are recreated if
|
||||
the system version of R changes.
|
||||
- #3206 issue by @lorenzwalthert.
|
||||
- #3265 PR by @lorenzwalthert.
|
||||
|
||||
3.7.1 - 2024-05-10
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix `language: rust` default language version check when `rust-toolchain.toml`
|
||||
is present.
|
||||
- issue by @gaborbernat.
|
||||
- #3201 PR by @asottile.
|
||||
|
||||
3.7.0 - 2024-03-24
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Use a tty for `docker` and `docker_image` hooks when `--color` is specified.
|
||||
- #3122 PR by @glehmann.
|
||||
|
||||
### Fixes
|
||||
- Fix `fail_fast` for individual hooks stopping when previous hooks had failed.
|
||||
- #3167 issue by @tp832944.
|
||||
- #3168 PR by @asottile.
|
||||
|
||||
### Updating
|
||||
- The per-hook behaviour of `fail_fast` was fixed. If you want the pre-3.7.0
|
||||
behaviour, add `fail_fast: true` to all hooks before the last `fail_fast`
|
||||
hook.
|
||||
|
||||
3.6.2 - 2024-02-18
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix building golang hooks during `git commit --all`.
|
||||
- #3130 PR by @asottile.
|
||||
- #2722 issue by @pestanko and @matthewhughes934.
|
||||
|
||||
3.6.1 - 2024-02-10
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Remove `PYTHONEXECUTABLE` from environment when running.
|
||||
- #3110 PR by @untitaker.
|
||||
- Handle staged-files-only with only a crlf diff.
|
||||
- #3126 PR by @asottile.
|
||||
- issue by @tyyrok.
|
||||
|
||||
3.6.0 - 2023-12-09
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Check `minimum_pre_commit_version` first when parsing configs.
|
||||
- #3092 PR by @asottile.
|
||||
|
||||
### Fixes
|
||||
- Fix deprecation warnings for `importlib.resources`.
|
||||
- #3043 PR by @asottile.
|
||||
- Fix deprecation warnings for rmtree.
|
||||
- #3079 PR by @edgarrmondragon.
|
||||
|
||||
### Updating
|
||||
- Drop support for python<3.9.
|
||||
- #3042 PR by @asottile.
|
||||
- #3093 PR by @asottile.
|
||||
|
||||
3.5.0 - 2023-10-13
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Improve performance of `check-hooks-apply` and `check-useless-excludes`.
|
||||
- #2998 PR by @mxr.
|
||||
- #2935 issue by @mxr.
|
||||
|
||||
### Fixes
|
||||
- Use `time.monotonic()` for more accurate hook timing.
|
||||
- #3024 PR by @adamchainz.
|
||||
|
||||
### Updating
|
||||
- Require npm 6.x+ for `language: node` hooks.
|
||||
- #2996 PR by @RoelAdriaans.
|
||||
- #1983 issue by @henryiii.
|
||||
|
||||
3.4.0 - 2023-09-02
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Add `language: haskell`.
|
||||
- #2932 by @alunduil.
|
||||
- Improve cpu count detection when run under cgroups.
|
||||
- #2979 PR by @jdb8.
|
||||
- #2978 issue by @jdb8.
|
||||
|
||||
### Fixes
|
||||
- Handle negative exit codes from hooks receiving posix signals.
|
||||
- #2971 PR by @chriskuehl.
|
||||
- #2970 issue by @chriskuehl.
|
||||
|
||||
3.3.3 - 2023-06-13
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Work around OS packagers setting `--install-dir` / `--bin-dir` in gem settings.
|
||||
- #2905 PR by @jaysoffian.
|
||||
- #2799 issue by @lmilbaum.
|
||||
|
||||
3.3.2 - 2023-05-17
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Work around `r` on windows sometimes double-un-quoting arguments.
|
||||
- #2885 PR by @lorenzwalthert.
|
||||
- #2870 issue by @lorenzwalthert.
|
||||
|
||||
3.3.1 - 2023-05-02
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Work around `git` partial clone bug for `autoupdate` on windows.
|
||||
- #2866 PR by @asottile.
|
||||
- #2865 issue by @adehad.
|
||||
|
||||
3.3.0 - 2023-05-01
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Upgrade ruby-build.
|
||||
- #2846 PR by @jalessio.
|
||||
- Use blobless clone for faster autoupdate.
|
||||
- #2859 PR by @asottile.
|
||||
- Add `-j` / `--jobs` argument to `autoupdate` for parallel execution.
|
||||
- #2863 PR by @asottile.
|
||||
- issue by @gaborbernat.
|
||||
|
||||
3.2.2 - 2023-04-03
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix support for swift >= 5.8.
|
||||
- #2836 PR by @edelabar.
|
||||
- #2835 issue by @kgrobelny-intive.
|
||||
|
||||
3.2.1 - 2023-03-25
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix `language_version` for `language: rust` without global `rustup`.
|
||||
- #2823 issue by @daschuer.
|
||||
- #2827 PR by @asottile.
|
||||
|
||||
3.2.0 - 2023-03-17
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Allow `pre-commit`, `pre-push`, and `pre-merge-commit` as `stages`.
|
||||
- #2732 issue by @asottile.
|
||||
- #2808 PR by @asottile.
|
||||
- Add `pre-rebase` hook support.
|
||||
- #2582 issue by @BrutalSimplicity.
|
||||
- #2725 PR by @mgaligniana.
|
||||
|
||||
### Fixes
|
||||
- Remove bulky cargo cache from `language: rust` installs.
|
||||
- #2820 PR by @asottile.
|
||||
|
||||
3.1.1 - 2023-02-27
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix `rust` with `language_version` and a non-writable host `RUSTUP_HOME`.
|
||||
- pre-commit-ci/issues#173 by @Swiftb0y.
|
||||
- #2788 by @asottile.
|
||||
|
||||
3.1.0 - 2023-02-22
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix `dotnet` for `.sln`-based hooks for dotnet>=7.0.200.
|
||||
- #2763 PR by @m-rsha.
|
||||
- Prevent stashing when `diff` fails to execute.
|
||||
- #2774 PR by @asottile.
|
||||
- #2773 issue by @strubbly.
|
||||
- Dependencies are no longer sorted in repository key.
|
||||
- #2776 PR by @asottile.
|
||||
|
||||
### Updating
|
||||
- Deprecate `language: python_venv`. Use `language: python` instead.
|
||||
- #2746 PR by @asottile.
|
||||
- #2734 issue by @asottile.
|
||||
|
||||
|
||||
3.0.4 - 2023-02-03
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Fix hook diff detection for files affected by `--textconv`.
|
||||
- #2743 PR by @adamchainz.
|
||||
- #2743 issue by @adamchainz.
|
||||
|
||||
3.0.3 - 2023-02-01
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Revert "Prevent local `Gemfile` from interfering with hook execution.".
|
||||
- #2739 issue by @Roguelazer.
|
||||
- #2740 PR by @asottile.
|
||||
|
||||
3.0.2 - 2023-01-29
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Prevent local `Gemfile` from interfering with hook execution.
|
||||
- #2727 PR by @asottile.
|
||||
- Fix `language: r`, `repo: local` hooks
|
||||
- pre-commit-ci/issues#107 by @lorenzwalthert.
|
||||
- #2728 PR by @asottile.
|
||||
|
||||
3.0.1 - 2023-01-26
|
||||
==================
|
||||
|
||||
### Fixes
|
||||
- Ensure coursier hooks are available offline after install.
|
||||
- #2723 PR by @asottile.
|
||||
|
||||
3.0.0 - 2023-01-23
|
||||
==================
|
||||
|
||||
### Features
|
||||
- Make `language: golang` bootstrap `go` if not present.
|
||||
- #2651 PR by @taoufik07.
|
||||
- #2649 issue by @taoufik07.
|
||||
- `language: coursier` now supports `additional_dependencies` and `repo: local`
|
||||
- #2702 PR by @asottile.
|
||||
- Upgrade `ruby-build` to `20221225`.
|
||||
- #2718 PR by @jalessio.
|
||||
|
||||
### Fixes
|
||||
- Improve error message for invalid yaml for `pre-commit autoupdate`.
|
||||
- #2686 PR by @asottile.
|
||||
- #2685 issue by @CarstenGrohmann.
|
||||
- `repo: local` no longer provisions an empty `git` repo.
|
||||
- #2699 PR by @asottile.
|
||||
|
||||
### Updating
|
||||
- Drop support for python<3.8
|
||||
- #2655 PR by @asottile.
|
||||
- Drop support for top-level list, use `pre-commit migrate-config` to update.
|
||||
- #2656 PR by @asottile.
|
||||
- Drop support for `sha` to specify revision, use `pre-commit migrate-config`
|
||||
to update.
|
||||
- #2657 PR by @asottile.
|
||||
- Remove `pre-commit-validate-config` and `pre-commit-validate-manifest`, use
|
||||
`pre-commit validate-config` and `pre-commit validate-manifest` instead.
|
||||
- #2658 PR by @asottile.
|
||||
- `language: golang` hooks must use `go.mod` to specify dependencies
|
||||
- #2672 PR by @taoufik07.
|
||||
|
||||
|
||||
2.21.0 - 2022-12-25
|
||||
===================
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ to implement. The current implemented languages are at varying levels:
|
|||
- 0th class - pre-commit does not require any dependencies for these languages
|
||||
as they're not actually languages (current examples: fail, pygrep)
|
||||
- 1st class - pre-commit will bootstrap a full interpreter requiring nothing to
|
||||
be installed globally (current examples: node, ruby, rust)
|
||||
be installed globally (current examples: go, node, ruby, rust)
|
||||
- 2nd class - pre-commit requires the user to install the language globally but
|
||||
will install tools in an isolated fashion (current examples: python, go,
|
||||
swift, docker).
|
||||
will install tools in an isolated fashion (current examples: python, swift,
|
||||
docker).
|
||||
- 3rd class - pre-commit requires the user to install both the tool and the
|
||||
language globally (current examples: script, system)
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ language, for example:
|
|||
|
||||
here are the apis that should be implemented for a language
|
||||
|
||||
Note that these are also documented in [`pre_commit/languages/all.py`](https://github.com/pre-commit/pre-commit/blob/main/pre_commit/languages/all.py)
|
||||
Note that these are also documented in [`pre_commit/lang_base.py`](https://github.com/pre-commit/pre-commit/blob/main/pre_commit/lang_base.py)
|
||||
|
||||
#### `ENVIRONMENT_DIR`
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ one cannot be determined, return `'default'`.
|
|||
You generally don't need to implement this on a first pass and can just use:
|
||||
|
||||
```python
|
||||
get_default_version = helpers.basic_default_version
|
||||
get_default_version = lang_base.basic_default_version
|
||||
```
|
||||
|
||||
`python` is currently the only language which implements this api
|
||||
|
|
@ -125,7 +125,7 @@ healthy.
|
|||
You generally don't need to implement this on a first pass and can just use:
|
||||
|
||||
```python
|
||||
health_check = helpers.basic_healthy_check
|
||||
health_check = lang_base.basic_health_check
|
||||
```
|
||||
|
||||
`python` is currently the only language which implements this api, for python
|
||||
|
|
@ -137,7 +137,7 @@ this is the trickiest one to implement and where all the smart parts happen.
|
|||
|
||||
this api should do the following things
|
||||
|
||||
- (0th / 3rd class): `install_environment = helpers.no_install`
|
||||
- (0th / 3rd class): `install_environment = lang_base.no_install`
|
||||
- (1st class): install a language runtime into the hook's directory
|
||||
- (2nd class): install the package at `.` into the `ENVIRONMENT_DIR`
|
||||
- (2nd class, optional): install packages listed in `additional_dependencies`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main)
|
||||
[](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main)
|
||||
[](https://github.com/pre-commit/pre-commit/actions/workflows/main.yml)
|
||||
[](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/main)
|
||||
|
||||
## pre-commit
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
trigger:
|
||||
branches:
|
||||
include: [main, test-me-*]
|
||||
tags:
|
||||
include: ['*']
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: asottile
|
||||
type: github
|
||||
endpoint: github
|
||||
name: asottile/azure-pipeline-templates
|
||||
ref: refs/tags/v2.4.1
|
||||
|
||||
jobs:
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [py37]
|
||||
os: windows
|
||||
additional_variables:
|
||||
TEMP: C:\Temp
|
||||
pre_test:
|
||||
- task: UseRubyVersion@0
|
||||
- powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts"
|
||||
displayName: Add conda to PATH
|
||||
- powershell: |
|
||||
Write-Host "##vso[task.prependpath]C:\Strawberry\perl\bin"
|
||||
Write-Host "##vso[task.prependpath]C:\Strawberry\perl\site\bin"
|
||||
Write-Host "##vso[task.prependpath]C:\Strawberry\c\bin"
|
||||
displayName: Add strawberry perl to PATH
|
||||
- bash: testing/get-dart.sh
|
||||
displayName: install dart
|
||||
- powershell: testing/get-r.ps1
|
||||
displayName: install R
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [py37]
|
||||
os: linux
|
||||
name_postfix: _latest_git
|
||||
pre_test:
|
||||
- task: UseRubyVersion@0
|
||||
- template: step--git-install.yml
|
||||
- bash: testing/get-coursier.sh
|
||||
displayName: install coursier
|
||||
- bash: testing/get-dart.sh
|
||||
displayName: install dart
|
||||
- bash: testing/get-lua.sh
|
||||
displayName: install lua
|
||||
- bash: testing/get-swift.sh
|
||||
displayName: install swift
|
||||
- bash: testing/get-r.sh
|
||||
displayName: install R
|
||||
- template: job--python-tox.yml@asottile
|
||||
parameters:
|
||||
toxenvs: [py37, py38, py39]
|
||||
os: linux
|
||||
pre_test:
|
||||
- task: UseRubyVersion@0
|
||||
- bash: testing/get-coursier.sh
|
||||
displayName: install coursier
|
||||
- bash: testing/get-dart.sh
|
||||
displayName: install dart
|
||||
- bash: testing/get-lua.sh
|
||||
displayName: install lua
|
||||
- bash: testing/get-swift.sh
|
||||
displayName: install swift
|
||||
- bash: testing/get-r.sh
|
||||
displayName: install R
|
||||
50
pre_commit/all_languages.py
Normal file
50
pre_commit/all_languages.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit.lang_base import Language
|
||||
from pre_commit.languages import conda
|
||||
from pre_commit.languages import coursier
|
||||
from pre_commit.languages import dart
|
||||
from pre_commit.languages import docker
|
||||
from pre_commit.languages import docker_image
|
||||
from pre_commit.languages import dotnet
|
||||
from pre_commit.languages import fail
|
||||
from pre_commit.languages import golang
|
||||
from pre_commit.languages import haskell
|
||||
from pre_commit.languages import julia
|
||||
from pre_commit.languages import lua
|
||||
from pre_commit.languages import node
|
||||
from pre_commit.languages import perl
|
||||
from pre_commit.languages import pygrep
|
||||
from pre_commit.languages import python
|
||||
from pre_commit.languages import r
|
||||
from pre_commit.languages import ruby
|
||||
from pre_commit.languages import rust
|
||||
from pre_commit.languages import swift
|
||||
from pre_commit.languages import unsupported
|
||||
from pre_commit.languages import unsupported_script
|
||||
|
||||
|
||||
languages: dict[str, Language] = {
|
||||
'conda': conda,
|
||||
'coursier': coursier,
|
||||
'dart': dart,
|
||||
'docker': docker,
|
||||
'docker_image': docker_image,
|
||||
'dotnet': dotnet,
|
||||
'fail': fail,
|
||||
'golang': golang,
|
||||
'haskell': haskell,
|
||||
'julia': julia,
|
||||
'lua': lua,
|
||||
'node': node,
|
||||
'perl': perl,
|
||||
'pygrep': pygrep,
|
||||
'python': python,
|
||||
'r': r,
|
||||
'ruby': ruby,
|
||||
'rust': rust,
|
||||
'swift': swift,
|
||||
'unsupported': unsupported,
|
||||
'unsupported_script': unsupported_script,
|
||||
}
|
||||
language_names = sorted(languages)
|
||||
|
|
@ -1,31 +1,43 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import logging
|
||||
import os.path
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Sequence
|
||||
from typing import NamedTuple
|
||||
|
||||
import cfgv
|
||||
from identify.identify import ALL_TAGS
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit.color import add_color_option
|
||||
from pre_commit.commands.validate_config import validate_config
|
||||
from pre_commit.commands.validate_manifest import validate_manifest
|
||||
from pre_commit.all_languages import language_names
|
||||
from pre_commit.errors import FatalError
|
||||
from pre_commit.languages.all import all_languages
|
||||
from pre_commit.logging_handler import logging_handler
|
||||
from pre_commit.util import parse_version
|
||||
from pre_commit.util import yaml_load
|
||||
from pre_commit.yaml import yaml_load
|
||||
|
||||
logger = logging.getLogger('pre_commit')
|
||||
|
||||
check_string_regex = cfgv.check_and(cfgv.check_string, cfgv.check_regex)
|
||||
|
||||
HOOK_TYPES = (
|
||||
'commit-msg',
|
||||
'post-checkout',
|
||||
'post-commit',
|
||||
'post-merge',
|
||||
'post-rewrite',
|
||||
'pre-commit',
|
||||
'pre-merge-commit',
|
||||
'pre-push',
|
||||
'pre-rebase',
|
||||
'prepare-commit-msg',
|
||||
)
|
||||
# `manual` is not invoked by any installed git hook. See #719
|
||||
STAGES = (*HOOK_TYPES, 'manual')
|
||||
|
||||
|
||||
def check_type_tag(tag: str) -> None:
|
||||
if tag not in ALL_TAGS:
|
||||
|
|
@ -35,6 +47,11 @@ def check_type_tag(tag: str) -> None:
|
|||
)
|
||||
|
||||
|
||||
def parse_version(s: str) -> tuple[int, ...]:
|
||||
"""poor man's version comparison"""
|
||||
return tuple(int(p) for p in s.split('.'))
|
||||
|
||||
|
||||
def check_min_version(version: str) -> None:
|
||||
if parse_version(version) > parse_version(C.VERSION):
|
||||
raise cfgv.ValidationError(
|
||||
|
|
@ -44,21 +61,186 @@ def check_min_version(version: str) -> None:
|
|||
)
|
||||
|
||||
|
||||
def _make_argparser(filenames_help: str) -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help=filenames_help)
|
||||
parser.add_argument('-V', '--version', action='version', version=C.VERSION)
|
||||
add_color_option(parser)
|
||||
return parser
|
||||
_STAGES = {
|
||||
'commit': 'pre-commit',
|
||||
'merge-commit': 'pre-merge-commit',
|
||||
'push': 'pre-push',
|
||||
}
|
||||
|
||||
|
||||
def transform_stage(stage: str) -> str:
|
||||
return _STAGES.get(stage, stage)
|
||||
|
||||
|
||||
MINIMAL_MANIFEST_SCHEMA = cfgv.Array(
|
||||
cfgv.Map(
|
||||
'Hook', 'id',
|
||||
cfgv.Required('id', cfgv.check_string),
|
||||
cfgv.Optional('stages', cfgv.check_array(cfgv.check_string), []),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def warn_for_stages_on_repo_init(repo: str, directory: str) -> None:
|
||||
try:
|
||||
manifest = cfgv.load_from_filename(
|
||||
os.path.join(directory, C.MANIFEST_FILE),
|
||||
schema=MINIMAL_MANIFEST_SCHEMA,
|
||||
load_strategy=yaml_load,
|
||||
exc_tp=InvalidManifestError,
|
||||
)
|
||||
except InvalidManifestError:
|
||||
return # they'll get a better error message when it actually loads!
|
||||
|
||||
legacy_stages = {} # sorted set
|
||||
for hook in manifest:
|
||||
for stage in hook.get('stages', ()):
|
||||
if stage in _STAGES:
|
||||
legacy_stages[stage] = True
|
||||
|
||||
if legacy_stages:
|
||||
logger.warning(
|
||||
f'repo `{repo}` uses deprecated stage names '
|
||||
f'({", ".join(legacy_stages)}) which will be removed in a '
|
||||
f'future version. '
|
||||
f'Hint: often `pre-commit autoupdate --repo {shlex.quote(repo)}` '
|
||||
f'will fix this. '
|
||||
f'if it does not -- consider reporting an issue to that repo.',
|
||||
)
|
||||
|
||||
|
||||
class StagesMigrationNoDefault(NamedTuple):
|
||||
key: str
|
||||
default: Sequence[str]
|
||||
|
||||
def check(self, dct: dict[str, Any]) -> None:
|
||||
if self.key not in dct:
|
||||
return
|
||||
|
||||
with cfgv.validate_context(f'At key: {self.key}'):
|
||||
val = dct[self.key]
|
||||
cfgv.check_array(cfgv.check_any)(val)
|
||||
|
||||
val = [transform_stage(v) for v in val]
|
||||
cfgv.check_array(cfgv.check_one_of(STAGES))(val)
|
||||
|
||||
def apply_default(self, dct: dict[str, Any]) -> None:
|
||||
if self.key not in dct:
|
||||
return
|
||||
dct[self.key] = [transform_stage(v) for v in dct[self.key]]
|
||||
|
||||
def remove_default(self, dct: dict[str, Any]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class StagesMigration(StagesMigrationNoDefault):
|
||||
def apply_default(self, dct: dict[str, Any]) -> None:
|
||||
dct.setdefault(self.key, self.default)
|
||||
super().apply_default(dct)
|
||||
|
||||
|
||||
class DeprecatedStagesWarning(NamedTuple):
|
||||
key: str
|
||||
|
||||
def check(self, dct: dict[str, Any]) -> None:
|
||||
if self.key not in dct:
|
||||
return
|
||||
|
||||
val = dct[self.key]
|
||||
cfgv.check_array(cfgv.check_any)(val)
|
||||
|
||||
legacy_stages = [stage for stage in val if stage in _STAGES]
|
||||
if legacy_stages:
|
||||
logger.warning(
|
||||
f'hook id `{dct["id"]}` uses deprecated stage names '
|
||||
f'({", ".join(legacy_stages)}) which will be removed in a '
|
||||
f'future version. '
|
||||
f'run: `pre-commit migrate-config` to automatically fix this.',
|
||||
)
|
||||
|
||||
def apply_default(self, dct: dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
def remove_default(self, dct: dict[str, Any]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DeprecatedDefaultStagesWarning(NamedTuple):
|
||||
key: str
|
||||
|
||||
def check(self, dct: dict[str, Any]) -> None:
|
||||
if self.key not in dct:
|
||||
return
|
||||
|
||||
val = dct[self.key]
|
||||
cfgv.check_array(cfgv.check_any)(val)
|
||||
|
||||
legacy_stages = [stage for stage in val if stage in _STAGES]
|
||||
if legacy_stages:
|
||||
logger.warning(
|
||||
f'top-level `default_stages` uses deprecated stage names '
|
||||
f'({", ".join(legacy_stages)}) which will be removed in a '
|
||||
f'future version. '
|
||||
f'run: `pre-commit migrate-config` to automatically fix this.',
|
||||
)
|
||||
|
||||
def apply_default(self, dct: dict[str, Any]) -> None:
|
||||
pass
|
||||
|
||||
def remove_default(self, dct: dict[str, Any]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _translate_language(name: str) -> str:
|
||||
return {
|
||||
'system': 'unsupported',
|
||||
'script': 'unsupported_script',
|
||||
}.get(name, name)
|
||||
|
||||
|
||||
class LanguageMigration(NamedTuple): # remove
|
||||
key: str
|
||||
check_fn: Callable[[object], None]
|
||||
|
||||
def check(self, dct: dict[str, Any]) -> None:
|
||||
if self.key not in dct:
|
||||
return
|
||||
|
||||
with cfgv.validate_context(f'At key: {self.key}'):
|
||||
self.check_fn(_translate_language(dct[self.key]))
|
||||
|
||||
def apply_default(self, dct: dict[str, Any]) -> None:
|
||||
if self.key not in dct:
|
||||
return
|
||||
|
||||
dct[self.key] = _translate_language(dct[self.key])
|
||||
|
||||
def remove_default(self, dct: dict[str, Any]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LanguageMigrationRequired(LanguageMigration): # replace with Required
|
||||
def check(self, dct: dict[str, Any]) -> None:
|
||||
if self.key not in dct:
|
||||
raise cfgv.ValidationError(f'Missing required key: {self.key}')
|
||||
|
||||
super().check(dct)
|
||||
|
||||
|
||||
MANIFEST_HOOK_DICT = cfgv.Map(
|
||||
'Hook', 'id',
|
||||
|
||||
# check first in case it uses some newer, incompatible feature
|
||||
cfgv.Optional(
|
||||
'minimum_pre_commit_version',
|
||||
cfgv.check_and(cfgv.check_string, check_min_version),
|
||||
'0',
|
||||
),
|
||||
|
||||
cfgv.Required('id', cfgv.check_string),
|
||||
cfgv.Required('name', cfgv.check_string),
|
||||
cfgv.Required('entry', cfgv.check_string),
|
||||
cfgv.Required('language', cfgv.check_one_of(all_languages)),
|
||||
LanguageMigrationRequired('language', cfgv.check_one_of(language_names)),
|
||||
cfgv.Optional('alias', cfgv.check_string, ''),
|
||||
|
||||
cfgv.Optional('files', check_string_regex, ''),
|
||||
|
|
@ -77,9 +259,8 @@ MANIFEST_HOOK_DICT = cfgv.Map(
|
|||
cfgv.Optional('description', cfgv.check_string, ''),
|
||||
cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT),
|
||||
cfgv.Optional('log_file', cfgv.check_string, ''),
|
||||
cfgv.Optional('minimum_pre_commit_version', cfgv.check_string, '0'),
|
||||
cfgv.Optional('require_serial', cfgv.check_bool, False),
|
||||
cfgv.Optional('stages', cfgv.check_array(cfgv.check_one_of(C.STAGES)), []),
|
||||
StagesMigration('stages', []),
|
||||
cfgv.Optional('verbose', cfgv.check_bool, False),
|
||||
)
|
||||
MANIFEST_SCHEMA = cfgv.Array(MANIFEST_HOOK_DICT)
|
||||
|
|
@ -89,33 +270,28 @@ class InvalidManifestError(FatalError):
|
|||
pass
|
||||
|
||||
|
||||
def _load_manifest_forward_compat(contents: str) -> object:
|
||||
obj = yaml_load(contents)
|
||||
if isinstance(obj, dict):
|
||||
check_min_version('5')
|
||||
raise AssertionError('unreachable')
|
||||
else:
|
||||
return obj
|
||||
|
||||
|
||||
load_manifest = functools.partial(
|
||||
cfgv.load_from_filename,
|
||||
schema=MANIFEST_SCHEMA,
|
||||
load_strategy=yaml_load,
|
||||
load_strategy=_load_manifest_forward_compat,
|
||||
exc_tp=InvalidManifestError,
|
||||
)
|
||||
|
||||
|
||||
def validate_manifest_main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = _make_argparser('Manifest filenames.')
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
with logging_handler(args.color):
|
||||
logger.warning(
|
||||
'pre-commit-validate-manifest is deprecated -- '
|
||||
'use `pre-commit validate-manifest` instead.',
|
||||
)
|
||||
|
||||
return validate_manifest(args.filenames)
|
||||
|
||||
|
||||
LOCAL = 'local'
|
||||
META = 'meta'
|
||||
|
||||
|
||||
# should inherit from cfgv.Conditional if sha support is dropped
|
||||
class WarnMutableRev(cfgv.ConditionalOptional):
|
||||
class WarnMutableRev(cfgv.Conditional):
|
||||
def check(self, dct: dict[str, Any]) -> None:
|
||||
super().check(dct)
|
||||
|
||||
|
|
@ -171,36 +347,6 @@ class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
|
|||
)
|
||||
|
||||
|
||||
class MigrateShaToRev:
|
||||
key = 'rev'
|
||||
|
||||
@staticmethod
|
||||
def _cond(key: str) -> cfgv.Conditional:
|
||||
return cfgv.Conditional(
|
||||
key, cfgv.check_string,
|
||||
condition_key='repo',
|
||||
condition_value=cfgv.NotIn(LOCAL, META),
|
||||
ensure_absent=True,
|
||||
)
|
||||
|
||||
def check(self, dct: dict[str, Any]) -> None:
|
||||
if dct.get('repo') in {LOCAL, META}:
|
||||
self._cond('rev').check(dct)
|
||||
self._cond('sha').check(dct)
|
||||
elif 'sha' in dct and 'rev' in dct:
|
||||
raise cfgv.ValidationError('Cannot specify both sha and rev')
|
||||
elif 'sha' in dct:
|
||||
self._cond('sha').check(dct)
|
||||
else:
|
||||
self._cond('rev').check(dct)
|
||||
|
||||
def apply_default(self, dct: dict[str, Any]) -> None:
|
||||
if 'sha' in dct:
|
||||
dct['rev'] = dct.pop('sha')
|
||||
|
||||
remove_default = cfgv.Required.remove_default
|
||||
|
||||
|
||||
def _entry(modname: str) -> str:
|
||||
"""the hook `entry` is passed through `shlex.split()` by the command
|
||||
runner, so to prevent issues with spaces and backslashes (on Windows)
|
||||
|
|
@ -258,12 +404,20 @@ class NotAllowed(cfgv.OptionalNoDefault):
|
|||
raise cfgv.ValidationError(f'{self.key!r} cannot be overridden')
|
||||
|
||||
|
||||
_COMMON_HOOK_WARNINGS = (
|
||||
OptionalSensibleRegexAtHook('files', cfgv.check_string),
|
||||
OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
|
||||
DeprecatedStagesWarning('stages'),
|
||||
)
|
||||
|
||||
META_HOOK_DICT = cfgv.Map(
|
||||
'Hook', 'id',
|
||||
cfgv.Required('id', cfgv.check_string),
|
||||
cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))),
|
||||
# language must be system
|
||||
cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'),
|
||||
# language must be `unsupported`
|
||||
cfgv.Optional(
|
||||
'language', cfgv.check_one_of({'unsupported'}), 'unsupported',
|
||||
),
|
||||
# entry cannot be overridden
|
||||
NotAllowed('entry', cfgv.check_any),
|
||||
*(
|
||||
|
|
@ -280,6 +434,7 @@ META_HOOK_DICT = cfgv.Map(
|
|||
item
|
||||
for item in MANIFEST_HOOK_DICT.items
|
||||
),
|
||||
*_COMMON_HOOK_WARNINGS,
|
||||
)
|
||||
CONFIG_HOOK_DICT = cfgv.Map(
|
||||
'Hook', 'id',
|
||||
|
|
@ -294,17 +449,18 @@ CONFIG_HOOK_DICT = cfgv.Map(
|
|||
cfgv.OptionalNoDefault(item.key, item.check_fn)
|
||||
for item in MANIFEST_HOOK_DICT.items
|
||||
if item.key != 'id'
|
||||
if item.key != 'stages'
|
||||
if item.key != 'language' # remove
|
||||
),
|
||||
OptionalSensibleRegexAtHook('files', cfgv.check_string),
|
||||
OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
|
||||
StagesMigrationNoDefault('stages', []),
|
||||
LanguageMigration('language', cfgv.check_one_of(language_names)), # remove
|
||||
*_COMMON_HOOK_WARNINGS,
|
||||
)
|
||||
LOCAL_HOOK_DICT = cfgv.Map(
|
||||
'Hook', 'id',
|
||||
|
||||
*MANIFEST_HOOK_DICT.items,
|
||||
|
||||
OptionalSensibleRegexAtHook('files', cfgv.check_string),
|
||||
OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
|
||||
*_COMMON_HOOK_WARNINGS,
|
||||
)
|
||||
CONFIG_REPO_DICT = cfgv.Map(
|
||||
'Repository', 'repo',
|
||||
|
|
@ -324,47 +480,43 @@ CONFIG_REPO_DICT = cfgv.Map(
|
|||
'repo', META,
|
||||
),
|
||||
|
||||
MigrateShaToRev(),
|
||||
WarnMutableRev(
|
||||
'rev',
|
||||
cfgv.check_string,
|
||||
'',
|
||||
'repo',
|
||||
cfgv.NotIn(LOCAL, META),
|
||||
True,
|
||||
'rev', cfgv.check_string,
|
||||
condition_key='repo',
|
||||
condition_value=cfgv.NotIn(LOCAL, META),
|
||||
ensure_absent=True,
|
||||
),
|
||||
cfgv.WarnAdditionalKeys(('repo', 'rev', 'hooks'), warn_unknown_keys_repo),
|
||||
)
|
||||
DEFAULT_LANGUAGE_VERSION = cfgv.Map(
|
||||
'DefaultLanguageVersion', None,
|
||||
cfgv.NoAdditionalKeys(all_languages),
|
||||
*(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in all_languages),
|
||||
cfgv.NoAdditionalKeys(language_names),
|
||||
*(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in language_names),
|
||||
)
|
||||
CONFIG_SCHEMA = cfgv.Map(
|
||||
'Config', None,
|
||||
|
||||
cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)),
|
||||
cfgv.Optional(
|
||||
'default_install_hook_types',
|
||||
cfgv.check_array(cfgv.check_one_of(C.HOOK_TYPES)),
|
||||
['pre-commit'],
|
||||
),
|
||||
cfgv.OptionalRecurse(
|
||||
'default_language_version', DEFAULT_LANGUAGE_VERSION, {},
|
||||
),
|
||||
cfgv.Optional(
|
||||
'default_stages',
|
||||
cfgv.check_array(cfgv.check_one_of(C.STAGES)),
|
||||
C.STAGES,
|
||||
),
|
||||
cfgv.Optional('files', check_string_regex, ''),
|
||||
cfgv.Optional('exclude', check_string_regex, '^$'),
|
||||
cfgv.Optional('fail_fast', cfgv.check_bool, False),
|
||||
# check first in case it uses some newer, incompatible feature
|
||||
cfgv.Optional(
|
||||
'minimum_pre_commit_version',
|
||||
cfgv.check_and(cfgv.check_string, check_min_version),
|
||||
'0',
|
||||
),
|
||||
|
||||
cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)),
|
||||
cfgv.Optional(
|
||||
'default_install_hook_types',
|
||||
cfgv.check_array(cfgv.check_one_of(HOOK_TYPES)),
|
||||
['pre-commit'],
|
||||
),
|
||||
cfgv.OptionalRecurse(
|
||||
'default_language_version', DEFAULT_LANGUAGE_VERSION, {},
|
||||
),
|
||||
StagesMigration('default_stages', STAGES),
|
||||
DeprecatedDefaultStagesWarning('default_stages'),
|
||||
cfgv.Optional('files', check_string_regex, ''),
|
||||
cfgv.Optional('exclude', check_string_regex, '^$'),
|
||||
cfgv.Optional('fail_fast', cfgv.check_bool, False),
|
||||
cfgv.WarnAdditionalKeys(
|
||||
(
|
||||
'repos',
|
||||
|
|
@ -391,35 +543,9 @@ class InvalidConfigError(FatalError):
|
|||
pass
|
||||
|
||||
|
||||
def ordered_load_normalize_legacy_config(contents: str) -> dict[str, Any]:
|
||||
data = yaml_load(contents)
|
||||
if isinstance(data, list):
|
||||
logger.warning(
|
||||
'normalizing pre-commit configuration to a top-level map. '
|
||||
'support for top level list will be removed in a future version. '
|
||||
'run: `pre-commit migrate-config` to automatically fix this.',
|
||||
)
|
||||
return {'repos': data}
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
load_config = functools.partial(
|
||||
cfgv.load_from_filename,
|
||||
schema=CONFIG_SCHEMA,
|
||||
load_strategy=ordered_load_normalize_legacy_config,
|
||||
load_strategy=yaml_load,
|
||||
exc_tp=InvalidConfigError,
|
||||
)
|
||||
|
||||
|
||||
def validate_config_main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = _make_argparser('Config filenames.')
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
with logging_handler(args.color):
|
||||
logger.warning(
|
||||
'pre-commit-validate-config is deprecated -- '
|
||||
'use `pre-commit validate-config` instead.',
|
||||
)
|
||||
|
||||
return validate_config(args.filenames)
|
||||
|
|
|
|||
|
|
@ -1,75 +1,85 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import os.path
|
||||
import re
|
||||
import tempfile
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import NamedTuple
|
||||
from typing import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import git
|
||||
from pre_commit import output
|
||||
from pre_commit import xargs
|
||||
from pre_commit.clientlib import InvalidManifestError
|
||||
from pre_commit.clientlib import load_config
|
||||
from pre_commit.clientlib import load_manifest
|
||||
from pre_commit.clientlib import LOCAL
|
||||
from pre_commit.clientlib import META
|
||||
from pre_commit.commands.migrate_config import migrate_config
|
||||
from pre_commit.store import Store
|
||||
from pre_commit.util import CalledProcessError
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import tmpdir
|
||||
from pre_commit.util import yaml_dump
|
||||
from pre_commit.util import yaml_load
|
||||
from pre_commit.yaml import yaml_dump
|
||||
from pre_commit.yaml import yaml_load
|
||||
|
||||
|
||||
class RevInfo(NamedTuple):
|
||||
repo: str
|
||||
rev: str
|
||||
frozen: str | None
|
||||
frozen: str | None = None
|
||||
hook_ids: frozenset[str] = frozenset()
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config: dict[str, Any]) -> RevInfo:
|
||||
return cls(config['repo'], config['rev'], None)
|
||||
return cls(config['repo'], config['rev'])
|
||||
|
||||
def update(self, tags_only: bool, freeze: bool) -> RevInfo:
|
||||
git_cmd = ('git', *git.NO_FS_MONITOR)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
_git = ('git', *git.NO_FS_MONITOR, '-C', tmp)
|
||||
|
||||
if tags_only:
|
||||
tag_cmd = (
|
||||
*git_cmd, 'describe',
|
||||
'FETCH_HEAD', '--tags', '--abbrev=0',
|
||||
)
|
||||
else:
|
||||
tag_cmd = (
|
||||
*git_cmd, 'describe',
|
||||
'FETCH_HEAD', '--tags', '--exact',
|
||||
)
|
||||
if tags_only:
|
||||
tag_opt = '--abbrev=0'
|
||||
else:
|
||||
tag_opt = '--exact'
|
||||
tag_cmd = (*_git, 'describe', 'FETCH_HEAD', '--tags', tag_opt)
|
||||
|
||||
with tmpdir() as tmp:
|
||||
git.init_repo(tmp, self.repo)
|
||||
cmd_output_b(*_git, 'config', 'extensions.partialClone', 'true')
|
||||
cmd_output_b(
|
||||
*git_cmd, 'fetch', 'origin', 'HEAD', '--tags',
|
||||
cwd=tmp,
|
||||
*_git, 'fetch', 'origin', 'HEAD',
|
||||
'--quiet', '--filter=blob:none', '--tags',
|
||||
)
|
||||
|
||||
try:
|
||||
rev = cmd_output(*tag_cmd, cwd=tmp)[1].strip()
|
||||
rev = cmd_output(*tag_cmd)[1].strip()
|
||||
except CalledProcessError:
|
||||
cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD')
|
||||
rev = cmd_output(*cmd, cwd=tmp)[1].strip()
|
||||
rev = cmd_output(*_git, 'rev-parse', 'FETCH_HEAD')[1].strip()
|
||||
else:
|
||||
if tags_only:
|
||||
rev = git.get_best_candidate_tag(rev, tmp)
|
||||
|
||||
frozen = None
|
||||
if freeze:
|
||||
exact_rev_cmd = (*git_cmd, 'rev-parse', rev)
|
||||
exact = cmd_output(*exact_rev_cmd, cwd=tmp)[1].strip()
|
||||
exact = cmd_output(*_git, 'rev-parse', rev)[1].strip()
|
||||
if exact != rev:
|
||||
rev, frozen = exact, rev
|
||||
return self._replace(rev=rev, frozen=frozen)
|
||||
|
||||
try:
|
||||
# workaround for windows -- see #2865
|
||||
cmd_output_b(*_git, 'show', f'{rev}:{C.MANIFEST_FILE}')
|
||||
cmd_output(*_git, 'checkout', rev, '--', C.MANIFEST_FILE)
|
||||
except CalledProcessError:
|
||||
pass # this will be caught by manifest validating code
|
||||
try:
|
||||
manifest = load_manifest(os.path.join(tmp, C.MANIFEST_FILE))
|
||||
except InvalidManifestError as e:
|
||||
raise RepositoryCannotBeUpdatedError(f'[{self.repo}] {e}')
|
||||
else:
|
||||
hook_ids = frozenset(hook['id'] for hook in manifest)
|
||||
|
||||
return self._replace(rev=rev, frozen=frozen, hook_ids=hook_ids)
|
||||
|
||||
|
||||
class RepositoryCannotBeUpdatedError(RuntimeError):
|
||||
|
|
@ -79,24 +89,30 @@ class RepositoryCannotBeUpdatedError(RuntimeError):
|
|||
def _check_hooks_still_exist_at_rev(
|
||||
repo_config: dict[str, Any],
|
||||
info: RevInfo,
|
||||
store: Store,
|
||||
) -> None:
|
||||
try:
|
||||
path = store.clone(repo_config['repo'], info.rev)
|
||||
manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE))
|
||||
except InvalidManifestError as e:
|
||||
raise RepositoryCannotBeUpdatedError(str(e))
|
||||
|
||||
# See if any of our hooks were deleted with the new commits
|
||||
hooks = {hook['id'] for hook in repo_config['hooks']}
|
||||
hooks_missing = hooks - {hook['id'] for hook in manifest}
|
||||
hooks_missing = hooks - info.hook_ids
|
||||
if hooks_missing:
|
||||
raise RepositoryCannotBeUpdatedError(
|
||||
f'Cannot update because the update target is missing these '
|
||||
f'hooks:\n{", ".join(sorted(hooks_missing))}',
|
||||
f'[{info.repo}] Cannot update because the update target is '
|
||||
f'missing these hooks: {", ".join(sorted(hooks_missing))}',
|
||||
)
|
||||
|
||||
|
||||
def _update_one(
|
||||
i: int,
|
||||
repo: dict[str, Any],
|
||||
*,
|
||||
tags_only: bool,
|
||||
freeze: bool,
|
||||
) -> tuple[int, RevInfo, RevInfo]:
|
||||
old = RevInfo.from_config(repo)
|
||||
new = old.update(tags_only=tags_only, freeze=freeze)
|
||||
_check_hooks_still_exist_at_rev(repo, new)
|
||||
return i, old, new
|
||||
|
||||
|
||||
REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$')
|
||||
|
||||
|
||||
|
|
@ -145,49 +161,53 @@ def _write_new_config(path: str, rev_infos: list[RevInfo | None]) -> None:
|
|||
|
||||
def autoupdate(
|
||||
config_file: str,
|
||||
store: Store,
|
||||
tags_only: bool,
|
||||
freeze: bool,
|
||||
repos: Sequence[str] = (),
|
||||
jobs: int = 1,
|
||||
) -> int:
|
||||
"""Auto-update the pre-commit config to the latest versions of repos."""
|
||||
migrate_config(config_file, quiet=True)
|
||||
retv = 0
|
||||
rev_infos: list[RevInfo | None] = []
|
||||
changed = False
|
||||
retv = 0
|
||||
|
||||
config = load_config(config_file)
|
||||
for repo_config in config['repos']:
|
||||
if repo_config['repo'] in {LOCAL, META}:
|
||||
continue
|
||||
config_repos = [
|
||||
repo for repo in load_config(config_file)['repos']
|
||||
if repo['repo'] not in {LOCAL, META}
|
||||
]
|
||||
|
||||
info = RevInfo.from_config(repo_config)
|
||||
if repos and info.repo not in repos:
|
||||
rev_infos.append(None)
|
||||
continue
|
||||
|
||||
output.write(f'Updating {info.repo} ... ')
|
||||
new_info = info.update(tags_only=tags_only, freeze=freeze)
|
||||
try:
|
||||
_check_hooks_still_exist_at_rev(repo_config, new_info, store)
|
||||
except RepositoryCannotBeUpdatedError as error:
|
||||
output.write_line(error.args[0])
|
||||
rev_infos.append(None)
|
||||
retv = 1
|
||||
continue
|
||||
|
||||
if new_info.rev != info.rev:
|
||||
changed = True
|
||||
if new_info.frozen:
|
||||
updated_to = f'{new_info.frozen} (frozen)'
|
||||
rev_infos: list[RevInfo | None] = [None] * len(config_repos)
|
||||
jobs = jobs or xargs.cpu_count() # 0 => number of cpus
|
||||
jobs = min(jobs, len(repos) or len(config_repos)) # max 1-per-thread
|
||||
jobs = max(jobs, 1) # at least one thread
|
||||
with concurrent.futures.ThreadPoolExecutor(jobs) as exe:
|
||||
futures = [
|
||||
exe.submit(
|
||||
_update_one,
|
||||
i, repo, tags_only=tags_only, freeze=freeze,
|
||||
)
|
||||
for i, repo in enumerate(config_repos)
|
||||
if not repos or repo['repo'] in repos
|
||||
]
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
try:
|
||||
i, old, new = future.result()
|
||||
except RepositoryCannotBeUpdatedError as e:
|
||||
output.write_line(str(e))
|
||||
retv = 1
|
||||
else:
|
||||
updated_to = new_info.rev
|
||||
msg = f'updating {info.rev} -> {updated_to}.'
|
||||
output.write_line(msg)
|
||||
rev_infos.append(new_info)
|
||||
else:
|
||||
output.write_line('already up to date.')
|
||||
rev_infos.append(None)
|
||||
if new.rev != old.rev:
|
||||
changed = True
|
||||
if new.frozen:
|
||||
new_s = f'{new.frozen} (frozen)'
|
||||
else:
|
||||
new_s = new.rev
|
||||
msg = f'updating {old.rev} -> {new_s}'
|
||||
rev_infos[i] = new
|
||||
else:
|
||||
msg = 'already up to date!'
|
||||
|
||||
output.write_line(f'[{old.repo}] {msg}')
|
||||
|
||||
if changed:
|
||||
_write_new_config(config_file, rev_infos)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from pre_commit.clientlib import load_manifest
|
|||
from pre_commit.clientlib import LOCAL
|
||||
from pre_commit.clientlib import META
|
||||
from pre_commit.store import Store
|
||||
from pre_commit.util import rmtree
|
||||
|
||||
|
||||
def _mark_used_repos(
|
||||
|
|
@ -26,7 +27,8 @@ def _mark_used_repos(
|
|||
for hook in repo['hooks']:
|
||||
deps = hook.get('additional_dependencies')
|
||||
unused_repos.discard((
|
||||
store.db_repo_name(repo['repo'], deps), C.LOCAL_REPO_VERSION,
|
||||
store.db_repo_name(repo['repo'], deps),
|
||||
C.LOCAL_REPO_VERSION,
|
||||
))
|
||||
else:
|
||||
key = (repo['repo'], repo['rev'])
|
||||
|
|
@ -56,34 +58,41 @@ def _mark_used_repos(
|
|||
))
|
||||
|
||||
|
||||
def _gc_repos(store: Store) -> int:
|
||||
configs = store.select_all_configs()
|
||||
repos = store.select_all_repos()
|
||||
def _gc(store: Store) -> int:
|
||||
with store.exclusive_lock(), store.connect() as db:
|
||||
store._create_configs_table(db)
|
||||
|
||||
# delete config paths which do not exist
|
||||
dead_configs = [p for p in configs if not os.path.exists(p)]
|
||||
live_configs = [p for p in configs if os.path.exists(p)]
|
||||
repos = db.execute('SELECT repo, ref, path FROM repos').fetchall()
|
||||
all_repos = {(repo, ref): path for repo, ref, path in repos}
|
||||
unused_repos = set(all_repos)
|
||||
|
||||
all_repos = {(repo, ref): path for repo, ref, path in repos}
|
||||
unused_repos = set(all_repos)
|
||||
for config_path in live_configs:
|
||||
try:
|
||||
config = load_config(config_path)
|
||||
except InvalidConfigError:
|
||||
dead_configs.append(config_path)
|
||||
continue
|
||||
else:
|
||||
for repo in config['repos']:
|
||||
_mark_used_repos(store, all_repos, unused_repos, repo)
|
||||
configs_rows = db.execute('SELECT path FROM configs').fetchall()
|
||||
configs = [path for path, in configs_rows]
|
||||
|
||||
store.delete_configs(dead_configs)
|
||||
for db_repo_name, ref in unused_repos:
|
||||
store.delete_repo(db_repo_name, ref, all_repos[(db_repo_name, ref)])
|
||||
return len(unused_repos)
|
||||
dead_configs = []
|
||||
for config_path in configs:
|
||||
try:
|
||||
config = load_config(config_path)
|
||||
except InvalidConfigError:
|
||||
dead_configs.append(config_path)
|
||||
continue
|
||||
else:
|
||||
for repo in config['repos']:
|
||||
_mark_used_repos(store, all_repos, unused_repos, repo)
|
||||
|
||||
paths = [(path,) for path in dead_configs]
|
||||
db.executemany('DELETE FROM configs WHERE path = ?', paths)
|
||||
|
||||
db.executemany(
|
||||
'DELETE FROM repos WHERE repo = ? and ref = ?',
|
||||
sorted(unused_repos),
|
||||
)
|
||||
for k in unused_repos:
|
||||
rmtree(all_repos[k])
|
||||
|
||||
return len(unused_repos)
|
||||
|
||||
|
||||
def gc(store: Store) -> int:
|
||||
with store.exclusive_lock():
|
||||
repos_removed = _gc_repos(store)
|
||||
output.write_line(f'{repos_removed} repo(s) removed.')
|
||||
output.write_line(f'{_gc(store)} repo(s) removed.')
|
||||
return 0
|
||||
|
|
|
|||
95
pre_commit/commands/hazmat.py
Normal file
95
pre_commit/commands/hazmat.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit.parse_shebang import normalize_cmd
|
||||
|
||||
|
||||
def add_parsers(parser: argparse.ArgumentParser) -> None:
|
||||
subparsers = parser.add_subparsers(dest='tool')
|
||||
|
||||
cd_parser = subparsers.add_parser(
|
||||
'cd', help='cd to a subdir and run the command',
|
||||
)
|
||||
cd_parser.add_argument('subdir')
|
||||
cd_parser.add_argument('cmd', nargs=argparse.REMAINDER)
|
||||
|
||||
ignore_exit_code_parser = subparsers.add_parser(
|
||||
'ignore-exit-code', help='run the command but ignore the exit code',
|
||||
)
|
||||
ignore_exit_code_parser.add_argument('cmd', nargs=argparse.REMAINDER)
|
||||
|
||||
n1_parser = subparsers.add_parser(
|
||||
'n1', help='run the command once per filename',
|
||||
)
|
||||
n1_parser.add_argument('cmd', nargs=argparse.REMAINDER)
|
||||
|
||||
|
||||
def _cmd_filenames(cmd: tuple[str, ...]) -> tuple[
|
||||
tuple[str, ...],
|
||||
tuple[str, ...],
|
||||
]:
|
||||
for idx, val in enumerate(reversed(cmd)):
|
||||
if val == '--':
|
||||
split = len(cmd) - idx
|
||||
break
|
||||
else:
|
||||
raise SystemExit('hazmat entry must end with `--`')
|
||||
|
||||
return cmd[:split - 1], cmd[split:]
|
||||
|
||||
|
||||
def cd(subdir: str, cmd: tuple[str, ...]) -> int:
|
||||
cmd, filenames = _cmd_filenames(cmd)
|
||||
|
||||
prefix = f'{subdir}/'
|
||||
new_filenames = []
|
||||
for filename in filenames:
|
||||
if not filename.startswith(prefix):
|
||||
raise SystemExit(f'unexpected file without {prefix=}: {filename}')
|
||||
else:
|
||||
new_filenames.append(filename.removeprefix(prefix))
|
||||
|
||||
cmd = normalize_cmd(cmd)
|
||||
return subprocess.call((*cmd, *new_filenames), cwd=subdir)
|
||||
|
||||
|
||||
def ignore_exit_code(cmd: tuple[str, ...]) -> int:
|
||||
cmd = normalize_cmd(cmd)
|
||||
subprocess.call(cmd)
|
||||
return 0
|
||||
|
||||
|
||||
def n1(cmd: tuple[str, ...]) -> int:
|
||||
cmd, filenames = _cmd_filenames(cmd)
|
||||
cmd = normalize_cmd(cmd)
|
||||
ret = 0
|
||||
for filename in filenames:
|
||||
ret |= subprocess.call((*cmd, filename))
|
||||
return ret
|
||||
|
||||
|
||||
def impl(args: argparse.Namespace) -> int:
|
||||
args.cmd = tuple(args.cmd)
|
||||
if args.tool == 'cd':
|
||||
return cd(args.subdir, args.cmd)
|
||||
elif args.tool == 'ignore-exit-code':
|
||||
return ignore_exit_code(args.cmd)
|
||||
elif args.tool == 'n1':
|
||||
return n1(args.cmd)
|
||||
else:
|
||||
raise NotImplementedError(f'unexpected tool: {args.tool}')
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
add_parsers(parser)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
return impl(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -4,7 +4,7 @@ import argparse
|
|||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit.commands.run import run
|
||||
from pre_commit.envcontext import envcontext
|
||||
|
|
@ -73,6 +73,8 @@ def _ns(
|
|||
local_branch: str | None = None,
|
||||
from_ref: str | None = None,
|
||||
to_ref: str | None = None,
|
||||
pre_rebase_upstream: str | None = None,
|
||||
pre_rebase_branch: str | None = None,
|
||||
remote_name: str | None = None,
|
||||
remote_url: str | None = None,
|
||||
commit_msg_filename: str | None = None,
|
||||
|
|
@ -84,11 +86,13 @@ def _ns(
|
|||
) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
color=color,
|
||||
hook_stage=hook_type.replace('pre-', ''),
|
||||
hook_stage=hook_type,
|
||||
remote_branch=remote_branch,
|
||||
local_branch=local_branch,
|
||||
from_ref=from_ref,
|
||||
to_ref=to_ref,
|
||||
pre_rebase_upstream=pre_rebase_upstream,
|
||||
pre_rebase_branch=pre_rebase_branch,
|
||||
remote_name=remote_name,
|
||||
remote_url=remote_url,
|
||||
commit_msg_filename=commit_msg_filename,
|
||||
|
|
@ -102,6 +106,7 @@ def _ns(
|
|||
hook=None,
|
||||
verbose=False,
|
||||
show_diff_on_failure=False,
|
||||
fail_fast=False,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -185,6 +190,12 @@ def _check_args_length(hook_type: str, args: Sequence[str]) -> None:
|
|||
f'hook-impl for {hook_type} expected 1, 2, or 3 arguments '
|
||||
f'but got {len(args)}: {args}',
|
||||
)
|
||||
elif hook_type == 'pre-rebase':
|
||||
if len(args) < 1 or len(args) > 2:
|
||||
raise SystemExit(
|
||||
f'hook-impl for {hook_type} expected 1 or 2 arguments '
|
||||
f'but got {len(args)}: {args}',
|
||||
)
|
||||
elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK:
|
||||
expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type]
|
||||
if len(args) != expected:
|
||||
|
|
@ -231,6 +242,13 @@ def _run_ns(
|
|||
return _ns(hook_type, color, is_squash_merge=args[0])
|
||||
elif hook_type == 'post-rewrite':
|
||||
return _ns(hook_type, color, rewrite_command=args[0])
|
||||
elif hook_type == 'pre-rebase' and len(args) == 1:
|
||||
return _ns(hook_type, color, pre_rebase_upstream=args[0])
|
||||
elif hook_type == 'pre-rebase' and len(args) == 2:
|
||||
return _ns(
|
||||
hook_type, color, pre_rebase_upstream=args[0],
|
||||
pre_rebase_branch=args[1],
|
||||
)
|
||||
else:
|
||||
raise AssertionError(f'unexpected hook type: {hook_type}')
|
||||
|
||||
|
|
|
|||
|
|
@ -103,8 +103,7 @@ def _install_hook_script(
|
|||
|
||||
hook_file.write(before + TEMPLATE_START)
|
||||
hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n')
|
||||
# TODO: python3.8+: shlex.join
|
||||
args_s = ' '.join(shlex.quote(part) for part in args)
|
||||
args_s = shlex.join(args)
|
||||
hook_file.write(f'ARGS=({args_s})\n')
|
||||
hook_file.write(TEMPLATE_END + after)
|
||||
make_executable(hook_path)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import functools
|
||||
import itertools
|
||||
import textwrap
|
||||
from collections.abc import Callable
|
||||
|
||||
import cfgv
|
||||
import yaml
|
||||
from yaml.nodes import ScalarNode
|
||||
|
||||
from pre_commit.util import yaml_load
|
||||
from pre_commit.clientlib import InvalidConfigError
|
||||
from pre_commit.yaml import yaml_compose
|
||||
from pre_commit.yaml import yaml_load
|
||||
from pre_commit.yaml_rewrite import MappingKey
|
||||
from pre_commit.yaml_rewrite import MappingValue
|
||||
from pre_commit.yaml_rewrite import match
|
||||
from pre_commit.yaml_rewrite import SequenceItem
|
||||
|
||||
|
||||
def _is_header_line(line: str) -> bool:
|
||||
|
|
@ -36,16 +46,84 @@ def _migrate_map(contents: str) -> str:
|
|||
return contents
|
||||
|
||||
|
||||
def _migrate_sha_to_rev(contents: str) -> str:
|
||||
return re.sub(r'(\n\s+)sha:', r'\1rev:', contents)
|
||||
def _preserve_style(n: ScalarNode, *, s: str) -> str:
|
||||
style = n.style or ''
|
||||
return f'{style}{s}{style}'
|
||||
|
||||
|
||||
def _fix_stage(n: ScalarNode) -> str:
|
||||
return _preserve_style(n, s=f'pre-{n.value}')
|
||||
|
||||
|
||||
def _migrate_composed(contents: str) -> str:
|
||||
tree = yaml_compose(contents)
|
||||
rewrites: list[tuple[ScalarNode, Callable[[ScalarNode], str]]] = []
|
||||
|
||||
# sha -> rev
|
||||
sha_to_rev_replace = functools.partial(_preserve_style, s='rev')
|
||||
sha_to_rev_matcher = (
|
||||
MappingValue('repos'),
|
||||
SequenceItem(),
|
||||
MappingKey('sha'),
|
||||
)
|
||||
for node in match(tree, sha_to_rev_matcher):
|
||||
rewrites.append((node, sha_to_rev_replace))
|
||||
|
||||
# python_venv -> python
|
||||
language_matcher = (
|
||||
MappingValue('repos'),
|
||||
SequenceItem(),
|
||||
MappingValue('hooks'),
|
||||
SequenceItem(),
|
||||
MappingValue('language'),
|
||||
)
|
||||
python_venv_replace = functools.partial(_preserve_style, s='python')
|
||||
for node in match(tree, language_matcher):
|
||||
if node.value == 'python_venv':
|
||||
rewrites.append((node, python_venv_replace))
|
||||
|
||||
# stages rewrites
|
||||
default_stages_matcher = (MappingValue('default_stages'), SequenceItem())
|
||||
default_stages_match = match(tree, default_stages_matcher)
|
||||
hook_stages_matcher = (
|
||||
MappingValue('repos'),
|
||||
SequenceItem(),
|
||||
MappingValue('hooks'),
|
||||
SequenceItem(),
|
||||
MappingValue('stages'),
|
||||
SequenceItem(),
|
||||
)
|
||||
hook_stages_match = match(tree, hook_stages_matcher)
|
||||
for node in itertools.chain(default_stages_match, hook_stages_match):
|
||||
if node.value in {'commit', 'push', 'merge-commit'}:
|
||||
rewrites.append((node, _fix_stage))
|
||||
|
||||
rewrites.sort(reverse=True, key=lambda nf: nf[0].start_mark.index)
|
||||
|
||||
src_parts = []
|
||||
end: int | None = None
|
||||
for node, func in rewrites:
|
||||
src_parts.append(contents[node.end_mark.index:end])
|
||||
src_parts.append(func(node))
|
||||
end = node.start_mark.index
|
||||
src_parts.append(contents[:end])
|
||||
src_parts.reverse()
|
||||
return ''.join(src_parts)
|
||||
|
||||
|
||||
def migrate_config(config_file: str, quiet: bool = False) -> int:
|
||||
with open(config_file) as f:
|
||||
orig_contents = contents = f.read()
|
||||
|
||||
with cfgv.reraise_as(InvalidConfigError):
|
||||
with cfgv.validate_context(f'File {config_file}'):
|
||||
try:
|
||||
yaml_load(orig_contents)
|
||||
except Exception as e:
|
||||
raise cfgv.ValidationError(str(e))
|
||||
|
||||
contents = _migrate_map(contents)
|
||||
contents = _migrate_sha_to_rev(contents)
|
||||
contents = _migrate_composed(contents)
|
||||
|
||||
if contents != orig_contents:
|
||||
with open(config_file, 'w') as f:
|
||||
|
|
|
|||
|
|
@ -9,19 +9,20 @@ import re
|
|||
import subprocess
|
||||
import time
|
||||
import unicodedata
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import MutableMapping
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Collection
|
||||
from typing import MutableMapping
|
||||
from typing import Sequence
|
||||
|
||||
from identify.identify import tags_from_path
|
||||
|
||||
from pre_commit import color
|
||||
from pre_commit import git
|
||||
from pre_commit import output
|
||||
from pre_commit.all_languages import languages
|
||||
from pre_commit.clientlib import load_config
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages.all import languages
|
||||
from pre_commit.repository import all_hooks
|
||||
from pre_commit.repository import install_hook_envs
|
||||
from pre_commit.staged_files_only import staged_files_only
|
||||
|
|
@ -57,37 +58,36 @@ def _full_msg(
|
|||
|
||||
|
||||
def filter_by_include_exclude(
|
||||
names: Collection[str],
|
||||
names: Iterable[str],
|
||||
include: str,
|
||||
exclude: str,
|
||||
) -> list[str]:
|
||||
) -> Generator[str]:
|
||||
include_re, exclude_re = re.compile(include), re.compile(exclude)
|
||||
return [
|
||||
return (
|
||||
filename for filename in names
|
||||
if include_re.search(filename)
|
||||
if not exclude_re.search(filename)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class Classifier:
|
||||
def __init__(self, filenames: Collection[str]) -> None:
|
||||
def __init__(self, filenames: Iterable[str]) -> None:
|
||||
self.filenames = [f for f in filenames if os.path.lexists(f)]
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
@functools.cache
|
||||
def _types_for_file(self, filename: str) -> set[str]:
|
||||
return tags_from_path(filename)
|
||||
|
||||
def by_types(
|
||||
self,
|
||||
names: Sequence[str],
|
||||
types: Collection[str],
|
||||
types_or: Collection[str],
|
||||
exclude_types: Collection[str],
|
||||
) -> list[str]:
|
||||
names: Iterable[str],
|
||||
types: Iterable[str],
|
||||
types_or: Iterable[str],
|
||||
exclude_types: Iterable[str],
|
||||
) -> Generator[str]:
|
||||
types = frozenset(types)
|
||||
types_or = frozenset(types_or)
|
||||
exclude_types = frozenset(exclude_types)
|
||||
ret = []
|
||||
for filename in names:
|
||||
tags = self._types_for_file(filename)
|
||||
if (
|
||||
|
|
@ -95,24 +95,24 @@ class Classifier:
|
|||
(not types_or or tags & types_or) and
|
||||
not tags & exclude_types
|
||||
):
|
||||
ret.append(filename)
|
||||
return ret
|
||||
yield filename
|
||||
|
||||
def filenames_for_hook(self, hook: Hook) -> tuple[str, ...]:
|
||||
names = self.filenames
|
||||
names = filter_by_include_exclude(names, hook.files, hook.exclude)
|
||||
names = self.by_types(
|
||||
names,
|
||||
def filenames_for_hook(self, hook: Hook) -> Generator[str]:
|
||||
return self.by_types(
|
||||
filter_by_include_exclude(
|
||||
self.filenames,
|
||||
hook.files,
|
||||
hook.exclude,
|
||||
),
|
||||
hook.types,
|
||||
hook.types_or,
|
||||
hook.exclude_types,
|
||||
)
|
||||
return tuple(names)
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
filenames: Collection[str],
|
||||
filenames: Iterable[str],
|
||||
include: str,
|
||||
exclude: str,
|
||||
) -> Classifier:
|
||||
|
|
@ -121,7 +121,7 @@ class Classifier:
|
|||
# this also makes improperly quoted shell-based hooks work better
|
||||
# see #1173
|
||||
if os.altsep == '/' and os.sep == '\\':
|
||||
filenames = [f.replace(os.sep, os.altsep) for f in filenames]
|
||||
filenames = (f.replace(os.sep, os.altsep) for f in filenames)
|
||||
filenames = filter_by_include_exclude(filenames, include, exclude)
|
||||
return Classifier(filenames)
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ def _run_single_hook(
|
|||
verbose: bool,
|
||||
use_color: bool,
|
||||
) -> tuple[bool, bytes]:
|
||||
filenames = classifier.filenames_for_hook(hook)
|
||||
filenames = tuple(classifier.filenames_for_hook(hook))
|
||||
|
||||
if hook.id in skips or hook.alias in skips:
|
||||
output.write(
|
||||
|
|
@ -187,10 +187,19 @@ def _run_single_hook(
|
|||
|
||||
if not hook.pass_filenames:
|
||||
filenames = ()
|
||||
time_before = time.time()
|
||||
time_before = time.monotonic()
|
||||
language = languages[hook.language]
|
||||
retcode, out = language.run_hook(hook, filenames, use_color)
|
||||
duration = round(time.time() - time_before, 2) or 0
|
||||
with language.in_env(hook.prefix, hook.language_version):
|
||||
retcode, out = language.run_hook(
|
||||
hook.prefix,
|
||||
hook.entry,
|
||||
hook.args,
|
||||
filenames,
|
||||
is_local=hook.src == 'local',
|
||||
require_serial=hook.require_serial,
|
||||
color=use_color,
|
||||
)
|
||||
duration = round(time.monotonic() - time_before, 2) or 0
|
||||
diff_after = _get_diff()
|
||||
|
||||
# if the hook makes changes, fail the commit
|
||||
|
|
@ -241,10 +250,11 @@ def _compute_cols(hooks: Sequence[Hook]) -> int:
|
|||
return max(cols, 80)
|
||||
|
||||
|
||||
def _all_filenames(args: argparse.Namespace) -> Collection[str]:
|
||||
def _all_filenames(args: argparse.Namespace) -> Iterable[str]:
|
||||
# these hooks do not operate on files
|
||||
if args.hook_stage in {
|
||||
'post-checkout', 'post-commit', 'post-merge', 'post-rewrite',
|
||||
'pre-rebase',
|
||||
}:
|
||||
return ()
|
||||
elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:
|
||||
|
|
@ -263,7 +273,8 @@ def _all_filenames(args: argparse.Namespace) -> Collection[str]:
|
|||
|
||||
def _get_diff() -> bytes:
|
||||
_, out, _ = cmd_output_b(
|
||||
'git', 'diff', '--no-ext-diff', '--ignore-submodules', check=False,
|
||||
'git', 'diff', '--no-ext-diff', '--no-textconv', '--ignore-submodules',
|
||||
check=False,
|
||||
)
|
||||
return out
|
||||
|
||||
|
|
@ -287,7 +298,8 @@ def _run_hooks(
|
|||
verbose=args.verbose, use_color=args.color,
|
||||
)
|
||||
retval |= current_retval
|
||||
if retval and (config['fail_fast'] or hook.fail_fast):
|
||||
fail_fast = (config['fail_fast'] or hook.fail_fast or args.fail_fast)
|
||||
if current_retval and fail_fast:
|
||||
break
|
||||
if retval and args.show_diff_on_failure and prior_diff:
|
||||
if args.all_files:
|
||||
|
|
@ -317,8 +329,7 @@ def _has_unmerged_paths() -> bool:
|
|||
|
||||
def _has_unstaged_config(config_file: str) -> bool:
|
||||
retcode, _, _ = cmd_output_b(
|
||||
'git', 'diff', '--no-ext-diff', '--exit-code', config_file,
|
||||
check=False,
|
||||
'git', 'diff', '--quiet', '--no-ext-diff', config_file, check=False,
|
||||
)
|
||||
# be explicit, other git errors don't mean it has an unstaged config.
|
||||
return retcode == 1
|
||||
|
|
@ -380,6 +391,10 @@ def run(
|
|||
environ['PRE_COMMIT_FROM_REF'] = args.from_ref
|
||||
environ['PRE_COMMIT_TO_REF'] = args.to_ref
|
||||
|
||||
if args.pre_rebase_upstream and args.pre_rebase_branch:
|
||||
environ['PRE_COMMIT_PRE_REBASE_UPSTREAM'] = args.pre_rebase_upstream
|
||||
environ['PRE_COMMIT_PRE_REBASE_BRANCH'] = args.pre_rebase_branch
|
||||
|
||||
if (
|
||||
args.remote_name and args.remote_url and
|
||||
args.remote_branch and args.local_branch
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import logging
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import git
|
||||
|
|
@ -11,9 +12,8 @@ from pre_commit.clientlib import load_manifest
|
|||
from pre_commit.commands.run import run
|
||||
from pre_commit.store import Store
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import tmpdir
|
||||
from pre_commit.util import yaml_dump
|
||||
from pre_commit.xargs import xargs
|
||||
from pre_commit.yaml import yaml_dump
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ def _repo_ref(tmpdir: str, repo: str, ref: str | None) -> tuple[str, str]:
|
|||
|
||||
|
||||
def try_repo(args: argparse.Namespace) -> int:
|
||||
with tmpdir() as tempdir:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo, ref = _repo_ref(tempdir, args.repo, args.ref)
|
||||
|
||||
store = Store(tempdir)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import clientlib
|
||||
|
||||
|
||||
def validate_config(filenames: list[str]) -> int:
|
||||
def validate_config(filenames: Sequence[str]) -> int:
|
||||
ret = 0
|
||||
|
||||
for filename in filenames:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import clientlib
|
||||
|
||||
|
||||
def validate_manifest(filenames: list[str]) -> int:
|
||||
def validate_manifest(filenames: Sequence[str]) -> int:
|
||||
ret = 0
|
||||
|
||||
for filename in filenames:
|
||||
|
|
|
|||
|
|
@ -1,33 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 8): # pragma: >=3.8 cover
|
||||
import importlib.metadata as importlib_metadata
|
||||
else: # pragma: <3.8 cover
|
||||
import importlib_metadata
|
||||
import importlib.metadata
|
||||
|
||||
CONFIG_FILE = '.pre-commit-config.yaml'
|
||||
MANIFEST_FILE = '.pre-commit-hooks.yaml'
|
||||
|
||||
# Bump when installation changes in a backwards / forwards incompatible way
|
||||
INSTALLED_STATE_VERSION = '1'
|
||||
# Bump when modifying `empty_template`
|
||||
LOCAL_REPO_VERSION = '1'
|
||||
|
||||
VERSION = importlib_metadata.version('pre_commit')
|
||||
|
||||
# `manual` is not invoked by any installed git hook. See #719
|
||||
STAGES = (
|
||||
'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
|
||||
'post-commit', 'manual', 'post-checkout', 'push', 'post-merge',
|
||||
'post-rewrite',
|
||||
)
|
||||
|
||||
HOOK_TYPES = (
|
||||
'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
|
||||
'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
|
||||
'post-rewrite',
|
||||
)
|
||||
VERSION = importlib.metadata.version('pre_commit')
|
||||
|
||||
DEFAULT = 'default'
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ from __future__ import annotations
|
|||
import contextlib
|
||||
import enum
|
||||
import os
|
||||
from typing import Generator
|
||||
from typing import MutableMapping
|
||||
from collections.abc import Generator
|
||||
from collections.abc import MutableMapping
|
||||
from typing import NamedTuple
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
_Unset = enum.Enum('_Unset', 'UNSET')
|
||||
|
|
@ -18,9 +17,9 @@ class Var(NamedTuple):
|
|||
default: str = ''
|
||||
|
||||
|
||||
SubstitutionT = Tuple[Union[str, Var], ...]
|
||||
SubstitutionT = tuple[Union[str, Var], ...]
|
||||
ValueT = Union[str, _Unset, SubstitutionT]
|
||||
PatchesT = Tuple[Tuple[str, ValueT], ...]
|
||||
PatchesT = tuple[tuple[str, ValueT], ...]
|
||||
|
||||
|
||||
def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str:
|
||||
|
|
@ -34,7 +33,7 @@ def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str:
|
|||
def envcontext(
|
||||
patch: PatchesT,
|
||||
_env: MutableMapping[str, str] | None = None,
|
||||
) -> Generator[None, None, None]:
|
||||
) -> Generator[None]:
|
||||
"""In this context, `os.environ` is modified according to `patch`.
|
||||
|
||||
`patch` is an iterable of 2-tuples (key, value):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import functools
|
|||
import os.path
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Generator
|
||||
from collections.abc import Generator
|
||||
from typing import IO
|
||||
|
||||
import pre_commit.constants as C
|
||||
|
|
@ -68,7 +68,7 @@ def _log_and_exit(
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def error_handler() -> Generator[None, None, None]:
|
||||
def error_handler() -> Generator[None]:
|
||||
try:
|
||||
yield
|
||||
except (Exception, KeyboardInterrupt) as e:
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from __future__ import annotations
|
|||
import contextlib
|
||||
import errno
|
||||
import sys
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Generator
|
||||
|
||||
|
||||
if sys.platform == 'win32': # pragma: no cover (windows)
|
||||
|
|
@ -20,7 +20,7 @@ if sys.platform == 'win32': # pragma: no cover (windows)
|
|||
def _locked(
|
||||
fileno: int,
|
||||
blocked_cb: Callable[[], None],
|
||||
) -> Generator[None, None, None]:
|
||||
) -> Generator[None]:
|
||||
try:
|
||||
msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region)
|
||||
except OSError:
|
||||
|
|
@ -53,7 +53,7 @@ else: # pragma: win32 no cover
|
|||
def _locked(
|
||||
fileno: int,
|
||||
blocked_cb: Callable[[], None],
|
||||
) -> Generator[None, None, None]:
|
||||
) -> Generator[None]:
|
||||
try:
|
||||
fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except OSError: # pragma: no cover (tests are single-threaded)
|
||||
|
|
@ -69,7 +69,7 @@ else: # pragma: win32 no cover
|
|||
def lock(
|
||||
path: str,
|
||||
blocked_cb: Callable[[], None],
|
||||
) -> Generator[None, None, None]:
|
||||
) -> Generator[None]:
|
||||
with open(path, 'a+') as f:
|
||||
with _locked(f.fileno(), blocked_cb):
|
||||
yield
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import logging
|
||||
import os.path
|
||||
import sys
|
||||
from typing import Mapping
|
||||
from collections.abc import Mapping
|
||||
|
||||
from pre_commit.errors import FatalError
|
||||
from pre_commit.util import CalledProcessError
|
||||
|
|
@ -93,11 +93,6 @@ def get_git_common_dir(git_root: str = '.') -> str:
|
|||
return get_git_dir(git_root)
|
||||
|
||||
|
||||
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 (
|
||||
|
|
@ -131,7 +126,7 @@ def get_conflicted_files() -> set[str]:
|
|||
merge_diff_filenames = zsplit(
|
||||
cmd_output(
|
||||
'git', 'diff', '--name-only', '--no-ext-diff', '-z',
|
||||
'-m', tree_hash, 'HEAD', 'MERGE_HEAD',
|
||||
'-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--',
|
||||
)[1],
|
||||
)
|
||||
return set(merge_conflict_filenames) | set(merge_diff_filenames)
|
||||
|
|
@ -224,7 +219,7 @@ def check_for_cygwin_mismatch() -> None:
|
|||
|
||||
if is_cygwin_python ^ is_cygwin_git:
|
||||
exe_type = {True: '(cygwin)', False: '(windows)'}
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
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'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shlex
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import NamedTuple
|
||||
from typing import Sequence
|
||||
|
||||
from pre_commit.prefix import Prefix
|
||||
|
||||
|
|
@ -37,10 +36,6 @@ class Hook(NamedTuple):
|
|||
stages: Sequence[str]
|
||||
verbose: bool
|
||||
|
||||
@property
|
||||
def cmd(self) -> tuple[str, ...]:
|
||||
return (*shlex.split(self.entry), *self.args)
|
||||
|
||||
@property
|
||||
def install_key(self) -> tuple[Prefix, str, str, tuple[str, ...]]:
|
||||
return (
|
||||
|
|
|
|||
196
pre_commit/lang_base.py
Normal file
196
pre_commit/lang_base.py
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import ContextManager
|
||||
from typing import NoReturn
|
||||
from typing import Protocol
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import parse_shebang
|
||||
from pre_commit import xargs
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import cmd_output_b
|
||||
|
||||
FIXED_RANDOM_SEED = 1542676187
|
||||
|
||||
SHIMS_RE = re.compile(r'[/\\]shims[/\\]')
|
||||
|
||||
|
||||
class Language(Protocol):
|
||||
# Use `None` for no installation / environment
|
||||
@property
|
||||
def ENVIRONMENT_DIR(self) -> str | None: ...
|
||||
# return a value to replace `'default` for `language_version`
|
||||
def get_default_version(self) -> str: ...
|
||||
# return whether the environment is healthy (or should be rebuilt)
|
||||
def health_check(self, prefix: Prefix, version: str) -> str | None: ...
|
||||
|
||||
# install a repository for the given language and language_version
|
||||
def install_environment(
|
||||
self,
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
# modify the environment for hook execution
|
||||
def in_env(self, prefix: Prefix, version: str) -> ContextManager[None]: ...
|
||||
|
||||
# execute a hook and return the exit code and output
|
||||
def run_hook(
|
||||
self,
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
...
|
||||
|
||||
|
||||
def exe_exists(exe: str) -> bool:
|
||||
found = parse_shebang.find_executable(exe)
|
||||
if found is None: # exe exists
|
||||
return False
|
||||
|
||||
homedir = os.path.expanduser('~')
|
||||
try:
|
||||
common: str | None = os.path.commonpath((found, homedir))
|
||||
except ValueError: # on windows, different drives raises ValueError
|
||||
common = None
|
||||
|
||||
return (
|
||||
# it is not in a /shims/ directory
|
||||
not SHIMS_RE.search(found) and
|
||||
(
|
||||
# the homedir is / (docker, service user, etc.)
|
||||
os.path.dirname(homedir) == homedir or
|
||||
# the exe is not contained in the home directory
|
||||
common != homedir
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None:
|
||||
cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs)
|
||||
|
||||
|
||||
def environment_dir(prefix: Prefix, d: str, language_version: str) -> str:
|
||||
return prefix.path(f'{d}-{language_version}')
|
||||
|
||||
|
||||
def assert_version_default(binary: str, version: str) -> None:
|
||||
if version != C.DEFAULT:
|
||||
raise AssertionError(
|
||||
f'for now, pre-commit requires system-installed {binary} -- '
|
||||
f'you selected `language_version: {version}`',
|
||||
)
|
||||
|
||||
|
||||
def assert_no_additional_deps(
|
||||
lang: str,
|
||||
additional_deps: Sequence[str],
|
||||
) -> None:
|
||||
if additional_deps:
|
||||
raise AssertionError(
|
||||
f'for now, pre-commit does not support '
|
||||
f'additional_dependencies for {lang} -- '
|
||||
f'you selected `additional_dependencies: {additional_deps}`',
|
||||
)
|
||||
|
||||
|
||||
def basic_get_default_version() -> str:
|
||||
return C.DEFAULT
|
||||
|
||||
|
||||
def basic_health_check(prefix: Prefix, language_version: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def no_install(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> NoReturn:
|
||||
raise AssertionError('This language is not installable')
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def no_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
yield
|
||||
|
||||
|
||||
def target_concurrency() -> int:
|
||||
if 'PRE_COMMIT_NO_CONCURRENCY' in os.environ:
|
||||
return 1
|
||||
else:
|
||||
# Travis appears to have a bunch of CPUs, but we can't use them all.
|
||||
if 'TRAVIS' in os.environ:
|
||||
return 2
|
||||
else:
|
||||
return xargs.cpu_count()
|
||||
|
||||
|
||||
def _shuffled(seq: Sequence[str]) -> list[str]:
|
||||
"""Deterministically shuffle"""
|
||||
fixed_random = random.Random()
|
||||
fixed_random.seed(FIXED_RANDOM_SEED, version=1)
|
||||
|
||||
seq = list(seq)
|
||||
fixed_random.shuffle(seq)
|
||||
return seq
|
||||
|
||||
|
||||
def run_xargs(
|
||||
cmd: tuple[str, ...],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
if require_serial:
|
||||
jobs = 1
|
||||
else:
|
||||
# Shuffle the files so that they more evenly fill out the xargs
|
||||
# partitions, but do it deterministically in case a hook cares about
|
||||
# ordering.
|
||||
file_args = _shuffled(file_args)
|
||||
jobs = target_concurrency()
|
||||
return xargs.xargs(cmd, file_args, target_concurrency=jobs, color=color)
|
||||
|
||||
|
||||
def hook_cmd(entry: str, args: Sequence[str]) -> tuple[str, ...]:
|
||||
cmd = shlex.split(entry)
|
||||
if cmd[:2] == ['pre-commit', 'hazmat']:
|
||||
cmd = [sys.executable, '-m', 'pre_commit.commands.hazmat', *cmd[2:]]
|
||||
return (*cmd, *args)
|
||||
|
||||
|
||||
def basic_run_hook(
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
return run_xargs(
|
||||
hook_cmd(entry, args),
|
||||
file_args,
|
||||
require_serial=require_serial,
|
||||
color=color,
|
||||
)
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
from typing import NamedTuple
|
||||
from typing import Sequence
|
||||
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import conda
|
||||
from pre_commit.languages import coursier
|
||||
from pre_commit.languages import dart
|
||||
from pre_commit.languages import docker
|
||||
from pre_commit.languages import docker_image
|
||||
from pre_commit.languages import dotnet
|
||||
from pre_commit.languages import fail
|
||||
from pre_commit.languages import golang
|
||||
from pre_commit.languages import lua
|
||||
from pre_commit.languages import node
|
||||
from pre_commit.languages import perl
|
||||
from pre_commit.languages import pygrep
|
||||
from pre_commit.languages import python
|
||||
from pre_commit.languages import r
|
||||
from pre_commit.languages import ruby
|
||||
from pre_commit.languages import rust
|
||||
from pre_commit.languages import script
|
||||
from pre_commit.languages import swift
|
||||
from pre_commit.languages import system
|
||||
from pre_commit.prefix import Prefix
|
||||
|
||||
|
||||
class Language(NamedTuple):
|
||||
name: str
|
||||
# Use `None` for no installation / environment
|
||||
ENVIRONMENT_DIR: str | None
|
||||
# return a value to replace `'default` for `language_version`
|
||||
get_default_version: Callable[[], str]
|
||||
# return whether the environment is healthy (or should be rebuilt)
|
||||
health_check: Callable[[Prefix, str], str | None]
|
||||
# install a repository for the given language and language_version
|
||||
install_environment: Callable[[Prefix, str, Sequence[str]], None]
|
||||
# execute a hook and return the exit code and output
|
||||
run_hook: Callable[[Hook, Sequence[str], bool], tuple[int, bytes]]
|
||||
|
||||
|
||||
# TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018
|
||||
languages = {
|
||||
# BEGIN GENERATED (testing/gen-languages-all)
|
||||
'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, health_check=conda.health_check, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501
|
||||
'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, health_check=coursier.health_check, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501
|
||||
'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, health_check=dart.health_check, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501
|
||||
'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, health_check=docker.health_check, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501
|
||||
'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, health_check=docker_image.health_check, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501
|
||||
'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, health_check=dotnet.health_check, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501
|
||||
'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, health_check=fail.health_check, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501
|
||||
'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, health_check=golang.health_check, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501
|
||||
'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, health_check=lua.health_check, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501
|
||||
'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, health_check=node.health_check, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501
|
||||
'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, health_check=perl.health_check, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501
|
||||
'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, health_check=pygrep.health_check, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501
|
||||
'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, health_check=python.health_check, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501
|
||||
'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, health_check=r.health_check, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501
|
||||
'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, health_check=ruby.health_check, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501
|
||||
'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, health_check=rust.health_check, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501
|
||||
'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, health_check=script.health_check, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501
|
||||
'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, health_check=swift.health_check, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501
|
||||
'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, health_check=system.health_check, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501
|
||||
# END GENERATED
|
||||
}
|
||||
# TODO: fully deprecate `python_venv`
|
||||
languages['python_venv'] = languages['python']
|
||||
all_languages = sorted(languages)
|
||||
|
|
@ -2,23 +2,23 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import os
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
import sys
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import SubstitutionT
|
||||
from pre_commit.envcontext import UNSET
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output_b
|
||||
|
||||
ENVIRONMENT_DIR = 'conda'
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def get_env_patch(env: str) -> PatchesT:
|
||||
|
|
@ -27,7 +27,7 @@ def get_env_patch(env: str) -> PatchesT:
|
|||
# $CONDA_PREFIX/Scripts and $CONDA_PREFIX. Whereas the latter only
|
||||
# seems to be used for python.exe.
|
||||
path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH'))
|
||||
if os.name == 'nt': # pragma: no cover (platform specific)
|
||||
if sys.platform == 'win32': # pragma: win32 cover
|
||||
path = (env, os.pathsep, *path)
|
||||
path = (os.path.join(env, 'Scripts'), os.pathsep, *path)
|
||||
path = (os.path.join(env, 'Library', 'bin'), os.pathsep, *path)
|
||||
|
|
@ -41,12 +41,8 @@ def get_env_patch(env: str) -> PatchesT:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version)
|
||||
envdir = prefix.path(directory)
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
|
@ -65,32 +61,17 @@ def install_environment(
|
|||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
helpers.assert_version_default('conda', version)
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
|
||||
lang_base.assert_version_default('conda', version)
|
||||
|
||||
conda_exe = _conda_exe()
|
||||
|
||||
env_dir = prefix.path(directory)
|
||||
with clean_path_on_failure(env_dir):
|
||||
env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
cmd_output_b(
|
||||
conda_exe, 'env', 'create', '-p', env_dir, '--file',
|
||||
'environment.yml', cwd=prefix.prefix_dir,
|
||||
)
|
||||
if additional_dependencies:
|
||||
cmd_output_b(
|
||||
conda_exe, 'env', 'create', '-p', env_dir, '--file',
|
||||
'environment.yml', cwd=prefix.prefix_dir,
|
||||
conda_exe, 'install', '-p', env_dir, *additional_dependencies,
|
||||
cwd=prefix.prefix_dir,
|
||||
)
|
||||
if additional_dependencies:
|
||||
cmd_output_b(
|
||||
conda_exe, 'install', '-p', env_dir, *additional_dependencies,
|
||||
cwd=prefix.prefix_dir,
|
||||
)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
# TODO: Some rare commands need to be run using `conda run` but mostly we
|
||||
# can run them without which is much quicker and produces a better
|
||||
# output.
|
||||
# cmd = ('conda', 'run', '-p', env_dir) + hook.cmd
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
|
|
|
|||
|
|
@ -1,81 +1,76 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
import os.path
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.errors import FatalError
|
||||
from pre_commit.parse_shebang import find_executable
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
|
||||
ENVIRONMENT_DIR = 'coursier'
|
||||
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None: # pragma: win32 no cover
|
||||
helpers.assert_version_default('coursier', version)
|
||||
helpers.assert_no_additional_deps('coursier', additional_dependencies)
|
||||
) -> None:
|
||||
lang_base.assert_version_default('coursier', version)
|
||||
|
||||
# Support both possible executable names (either "cs" or "coursier")
|
||||
executable = find_executable('cs') or find_executable('coursier')
|
||||
if executable is None:
|
||||
cs = find_executable('cs') or find_executable('coursier')
|
||||
if cs is None:
|
||||
raise AssertionError(
|
||||
'pre-commit requires system-installed "cs" or "coursier" '
|
||||
'executables in the application search path',
|
||||
)
|
||||
|
||||
envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
|
||||
channel = prefix.path('.pre-commit-channel')
|
||||
with clean_path_on_failure(envdir):
|
||||
for app_descriptor in os.listdir(channel):
|
||||
_, app_file = os.path.split(app_descriptor)
|
||||
app, _ = os.path.splitext(app_file)
|
||||
helpers.run_setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
executable,
|
||||
'install',
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
def _install(*opts: str) -> None:
|
||||
assert cs is not None
|
||||
lang_base.setup_cmd(prefix, (cs, 'fetch', *opts))
|
||||
lang_base.setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts))
|
||||
|
||||
with in_env(prefix, version):
|
||||
channel = prefix.path('.pre-commit-channel')
|
||||
if os.path.isdir(channel):
|
||||
for app_descriptor in os.listdir(channel):
|
||||
_, app_file = os.path.split(app_descriptor)
|
||||
app, _ = os.path.splitext(app_file)
|
||||
_install(
|
||||
'--default-channels=false',
|
||||
f'--channel={channel}',
|
||||
'--channel', channel,
|
||||
app,
|
||||
f'--dir={envdir}',
|
||||
),
|
||||
)
|
||||
elif not additional_dependencies:
|
||||
raise FatalError(
|
||||
'expected .pre-commit-channel dir or additional_dependencies',
|
||||
)
|
||||
|
||||
if additional_dependencies:
|
||||
_install(*additional_dependencies)
|
||||
|
||||
def get_env_patch(target_dir: str) -> PatchesT: # pragma: win32 no cover
|
||||
|
||||
def get_env_patch(target_dir: str) -> PatchesT:
|
||||
return (
|
||||
('PATH', (target_dir, os.pathsep, Var('PATH'))),
|
||||
('COURSIER_CACHE', os.path.join(target_dir, '.cs-cache')),
|
||||
)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
) -> Generator[None, None, None]: # pragma: win32 no cover
|
||||
target_dir = prefix.path(
|
||||
helpers.environment_dir(ENVIRONMENT_DIR, get_default_version()),
|
||||
)
|
||||
with envcontext(get_env_patch(target_dir)):
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]: # pragma: win32 no cover
|
||||
with in_env(hook.prefix):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
|
|
|
|||
|
|
@ -4,24 +4,22 @@ import contextlib
|
|||
import os.path
|
||||
import shutil
|
||||
import tempfile
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import win_exe
|
||||
from pre_commit.util import yaml_load
|
||||
from pre_commit.yaml import yaml_load
|
||||
|
||||
ENVIRONMENT_DIR = 'dartenv'
|
||||
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def get_env_patch(venv: str) -> PatchesT:
|
||||
|
|
@ -31,9 +29,8 @@ def get_env_patch(venv: str) -> PatchesT:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(prefix: Prefix) -> Generator[None, None, None]:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT)
|
||||
envdir = prefix.path(directory)
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
|
@ -43,9 +40,9 @@ def install_environment(
|
|||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
helpers.assert_version_default('dart', version)
|
||||
lang_base.assert_version_default('dart', version)
|
||||
|
||||
envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
bin_dir = os.path.join(envdir, 'bin')
|
||||
|
||||
def _install_dir(prefix_p: Prefix, pub_cache: str) -> None:
|
||||
|
|
@ -54,10 +51,10 @@ def install_environment(
|
|||
with open(prefix_p.path('pubspec.yaml')) as f:
|
||||
pubspec_contents = yaml_load(f)
|
||||
|
||||
helpers.run_setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env)
|
||||
lang_base.setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env)
|
||||
|
||||
for executable in pubspec_contents['executables']:
|
||||
helpers.run_setup_cmd(
|
||||
lang_base.setup_cmd(
|
||||
prefix_p,
|
||||
(
|
||||
'dart', 'compile', 'exe',
|
||||
|
|
@ -67,44 +64,34 @@ def install_environment(
|
|||
env=dart_env,
|
||||
)
|
||||
|
||||
with clean_path_on_failure(envdir):
|
||||
os.makedirs(bin_dir)
|
||||
os.makedirs(bin_dir)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
_install_dir(prefix, tmp)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
_install_dir(prefix, tmp)
|
||||
|
||||
for dep_s in additional_dependencies:
|
||||
with tempfile.TemporaryDirectory() as dep_tmp:
|
||||
dep, _, version = dep_s.partition(':')
|
||||
if version:
|
||||
dep_cmd: tuple[str, ...] = (dep, '--version', version)
|
||||
else:
|
||||
dep_cmd = (dep,)
|
||||
for dep_s in additional_dependencies:
|
||||
with tempfile.TemporaryDirectory() as dep_tmp:
|
||||
dep, _, version = dep_s.partition(':')
|
||||
if version:
|
||||
dep_cmd: tuple[str, ...] = (dep, '--version', version)
|
||||
else:
|
||||
dep_cmd = (dep,)
|
||||
|
||||
helpers.run_setup_cmd(
|
||||
prefix,
|
||||
('dart', 'pub', 'cache', 'add', *dep_cmd),
|
||||
env={**os.environ, 'PUB_CACHE': dep_tmp},
|
||||
lang_base.setup_cmd(
|
||||
prefix,
|
||||
('dart', 'pub', 'cache', 'add', *dep_cmd),
|
||||
env={**os.environ, 'PUB_CACHE': dep_tmp},
|
||||
)
|
||||
|
||||
# try and find the 'pubspec.yaml' that just got added
|
||||
for root, _, filenames in os.walk(dep_tmp):
|
||||
if 'pubspec.yaml' in filenames:
|
||||
with tempfile.TemporaryDirectory() as copied:
|
||||
pkg = os.path.join(copied, 'pkg')
|
||||
shutil.copytree(root, pkg)
|
||||
_install_dir(Prefix(pkg), dep_tmp)
|
||||
break
|
||||
else:
|
||||
raise AssertionError(
|
||||
f'could not find pubspec.yaml for {dep_s}',
|
||||
)
|
||||
|
||||
# try and find the 'pubspec.yaml' that just got added
|
||||
for root, _, filenames in os.walk(dep_tmp):
|
||||
if 'pubspec.yaml' in filenames:
|
||||
with tempfile.TemporaryDirectory() as copied:
|
||||
pkg = os.path.join(copied, 'pkg')
|
||||
shutil.copytree(root, pkg)
|
||||
_install_dir(Prefix(pkg), dep_tmp)
|
||||
break
|
||||
else:
|
||||
raise AssertionError(
|
||||
f'could not find pubspec.yaml for {dep_s}',
|
||||
)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
|
|
|
|||
|
|
@ -1,48 +1,51 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from typing import Sequence
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import CalledProcessError
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output_b
|
||||
|
||||
ENVIRONMENT_DIR = 'docker'
|
||||
PRE_COMMIT_LABEL = 'PRE_COMMIT'
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
in_env = lang_base.no_env # no special environment for docker
|
||||
|
||||
_HOSTNAME_MOUNT_RE = re.compile(
|
||||
rb"""
|
||||
/containers
|
||||
(?:/overlay-containers)?
|
||||
/([a-z0-9]{64})
|
||||
(?:/userdata)?
|
||||
/hostname
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
def _is_in_docker() -> bool:
|
||||
try:
|
||||
with open('/proc/1/cgroup', 'rb') as f:
|
||||
return b'docker' in f.read()
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
def _get_container_id() -> str | None:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
with open('/proc/1/mountinfo', 'rb') as f:
|
||||
for line in f:
|
||||
m = _HOSTNAME_MOUNT_RE.search(line)
|
||||
if m:
|
||||
return m[1].decode()
|
||||
|
||||
|
||||
def _get_container_id() -> str:
|
||||
# It's assumed that we already check /proc/1/cgroup in _is_in_docker. The
|
||||
# cpuset cgroup controller existed since cgroups were introduced so this
|
||||
# way of getting the container ID is pretty reliable.
|
||||
with open('/proc/1/cgroup', 'rb') as f:
|
||||
for line in f.readlines():
|
||||
if line.split(b':')[1] == b'cpuset':
|
||||
return os.path.basename(line.split(b':')[2]).strip().decode()
|
||||
raise RuntimeError('Failed to find the container ID in /proc/1/cgroup.')
|
||||
return None
|
||||
|
||||
|
||||
def _get_docker_path(path: str) -> str:
|
||||
if not _is_in_docker():
|
||||
return path
|
||||
|
||||
container_id = _get_container_id()
|
||||
if container_id is None:
|
||||
return path
|
||||
|
||||
try:
|
||||
_, out, _ = cmd_output_b('docker', 'inspect', container_id)
|
||||
|
|
@ -86,37 +89,64 @@ def build_docker_image(
|
|||
cmd += ('--pull',)
|
||||
# This must come last for old versions of docker. See #477
|
||||
cmd += ('.',)
|
||||
helpers.run_setup_cmd(prefix, cmd)
|
||||
lang_base.setup_cmd(prefix, cmd)
|
||||
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix, version: str, additional_dependencies: Sequence[str],
|
||||
) -> None: # pragma: win32 no cover
|
||||
helpers.assert_version_default('docker', version)
|
||||
helpers.assert_no_additional_deps('docker', additional_dependencies)
|
||||
lang_base.assert_version_default('docker', version)
|
||||
lang_base.assert_no_additional_deps('docker', additional_dependencies)
|
||||
|
||||
directory = prefix.path(
|
||||
helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
|
||||
)
|
||||
directory = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
# Docker doesn't really have relevant disk environment, but pre-commit
|
||||
# still needs to cleanup its state files on failure
|
||||
with clean_path_on_failure(directory):
|
||||
build_docker_image(prefix, pull=True)
|
||||
os.mkdir(directory)
|
||||
build_docker_image(prefix, pull=True)
|
||||
os.mkdir(directory)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _is_rootless() -> bool: # pragma: win32 no cover
|
||||
retcode, out, _ = cmd_output_b(
|
||||
'docker', 'system', 'info', '--format', '{{ json . }}',
|
||||
)
|
||||
if retcode != 0:
|
||||
return False
|
||||
|
||||
info = json.loads(out)
|
||||
try:
|
||||
return (
|
||||
# docker:
|
||||
# https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemInfo
|
||||
'name=rootless' in (info.get('SecurityOptions') or ()) or
|
||||
# podman:
|
||||
# https://docs.podman.io/en/latest/_static/api.html?version=v5.4#tag/system/operation/SystemInfoLibpod
|
||||
info['host']['security']['rootless']
|
||||
)
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover
|
||||
if _is_rootless():
|
||||
return ()
|
||||
|
||||
try:
|
||||
return ('-u', f'{os.getuid()}:{os.getgid()}')
|
||||
except AttributeError:
|
||||
return ()
|
||||
|
||||
|
||||
def docker_cmd() -> tuple[str, ...]: # pragma: win32 no cover
|
||||
def get_docker_tty(*, color: bool) -> tuple[str, ...]: # pragma: win32 no cover # noqa: E501
|
||||
return (('--tty',) if color else ())
|
||||
|
||||
|
||||
def docker_cmd(*, color: bool) -> tuple[str, ...]: # pragma: win32 no cover
|
||||
return (
|
||||
'docker', 'run',
|
||||
'--rm',
|
||||
*get_docker_tty(color=color),
|
||||
*get_docker_user(),
|
||||
# https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from
|
||||
# The `Z` option tells Docker to label the content with a private
|
||||
|
|
@ -127,16 +157,25 @@ def docker_cmd() -> tuple[str, ...]: # pragma: win32 no cover
|
|||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]: # pragma: win32 no cover
|
||||
# Rebuild the docker image in case it has gone missing, as many people do
|
||||
# automated cleanup of docker images.
|
||||
build_docker_image(hook.prefix, pull=False)
|
||||
build_docker_image(prefix, pull=False)
|
||||
|
||||
entry_exe, *cmd_rest = hook.cmd
|
||||
entry_exe, *cmd_rest = lang_base.hook_cmd(entry, args)
|
||||
|
||||
entry_tag = ('--entrypoint', entry_exe, docker_tag(hook.prefix))
|
||||
cmd = (*docker_cmd(), *entry_tag, *cmd_rest)
|
||||
return helpers.run_xargs(hook, cmd, file_args, color=color)
|
||||
entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix))
|
||||
return lang_base.run_xargs(
|
||||
(*docker_cmd(color=color), *entry_tag, *cmd_rest),
|
||||
file_args,
|
||||
require_serial=require_serial,
|
||||
color=color,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,32 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.languages.docker import docker_cmd
|
||||
from pre_commit.prefix import Prefix
|
||||
|
||||
ENVIRONMENT_DIR = None
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
install_environment = helpers.no_install
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
install_environment = lang_base.no_install
|
||||
in_env = lang_base.no_env
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]: # pragma: win32 no cover
|
||||
cmd = docker_cmd() + hook.cmd
|
||||
return helpers.run_xargs(hook, cmd, file_args, color=color)
|
||||
cmd = docker_cmd(color=color) + lang_base.hook_cmd(entry, args)
|
||||
return lang_base.run_xargs(
|
||||
cmd,
|
||||
file_args,
|
||||
require_serial=require_serial,
|
||||
color=color,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,23 +6,21 @@ import re
|
|||
import tempfile
|
||||
import xml.etree.ElementTree
|
||||
import zipfile
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
|
||||
ENVIRONMENT_DIR = 'dotnetenv'
|
||||
BIN_DIR = 'bin'
|
||||
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def get_env_patch(venv: str) -> PatchesT:
|
||||
|
|
@ -32,15 +30,14 @@ def get_env_patch(venv: str) -> PatchesT:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(prefix: Prefix) -> Generator[None, None, None]:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT)
|
||||
envdir = prefix.path(directory)
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _nuget_config_no_sources() -> Generator[str, None, None]:
|
||||
def _nuget_config_no_sources() -> Generator[str]:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
nuget_config = os.path.join(tmpdir, 'nuget.config')
|
||||
with open(nuget_config, 'w') as f:
|
||||
|
|
@ -60,69 +57,55 @@ def install_environment(
|
|||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
helpers.assert_version_default('dotnet', version)
|
||||
helpers.assert_no_additional_deps('dotnet', additional_dependencies)
|
||||
lang_base.assert_version_default('dotnet', version)
|
||||
lang_base.assert_no_additional_deps('dotnet', additional_dependencies)
|
||||
|
||||
envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
|
||||
with clean_path_on_failure(envdir):
|
||||
build_dir = 'pre-commit-build'
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
build_dir = prefix.path('pre-commit-build')
|
||||
|
||||
# Build & pack nupkg file
|
||||
helpers.run_setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
'dotnet', 'pack',
|
||||
'--configuration', 'Release',
|
||||
'--output', build_dir,
|
||||
),
|
||||
)
|
||||
# Build & pack nupkg file
|
||||
lang_base.setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
'dotnet', 'pack',
|
||||
'--configuration', 'Release',
|
||||
'--property', f'PackageOutputPath={build_dir}',
|
||||
),
|
||||
)
|
||||
|
||||
nupkg_dir = prefix.path(build_dir)
|
||||
nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')]
|
||||
nupkg_dir = prefix.path(build_dir)
|
||||
nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')]
|
||||
|
||||
if not nupkgs:
|
||||
raise AssertionError('could not find any build outputs to install')
|
||||
if not nupkgs:
|
||||
raise AssertionError('could not find any build outputs to install')
|
||||
|
||||
for nupkg in nupkgs:
|
||||
with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f:
|
||||
nuspec, = (x for x in f.namelist() if x.endswith('.nuspec'))
|
||||
with f.open(nuspec) as spec:
|
||||
tree = xml.etree.ElementTree.parse(spec)
|
||||
for nupkg in nupkgs:
|
||||
with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f:
|
||||
nuspec, = (x for x in f.namelist() if x.endswith('.nuspec'))
|
||||
with f.open(nuspec) as spec:
|
||||
tree = xml.etree.ElementTree.parse(spec)
|
||||
|
||||
namespace = re.match(r'{.*}', tree.getroot().tag)
|
||||
if not namespace:
|
||||
raise AssertionError('could not parse namespace from nuspec')
|
||||
namespace = re.match(r'{.*}', tree.getroot().tag)
|
||||
if not namespace:
|
||||
raise AssertionError('could not parse namespace from nuspec')
|
||||
|
||||
tool_id_element = tree.find(f'.//{namespace[0]}id')
|
||||
if tool_id_element is None:
|
||||
raise AssertionError('expected to find an "id" element')
|
||||
tool_id_element = tree.find(f'.//{namespace[0]}id')
|
||||
if tool_id_element is None:
|
||||
raise AssertionError('expected to find an "id" element')
|
||||
|
||||
tool_id = tool_id_element.text
|
||||
if not tool_id:
|
||||
raise AssertionError('"id" element missing tool name')
|
||||
tool_id = tool_id_element.text
|
||||
if not tool_id:
|
||||
raise AssertionError('"id" element missing tool name')
|
||||
|
||||
# Install to bin dir
|
||||
with _nuget_config_no_sources() as nuget_config:
|
||||
helpers.run_setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
'dotnet', 'tool', 'install',
|
||||
'--configfile', nuget_config,
|
||||
'--tool-path', os.path.join(envdir, BIN_DIR),
|
||||
'--add-source', build_dir,
|
||||
tool_id,
|
||||
),
|
||||
)
|
||||
|
||||
# Clean the git dir, ignoring the environment dir
|
||||
clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*')
|
||||
helpers.run_setup_cmd(prefix, clean_cmd)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
# Install to bin dir
|
||||
with _nuget_config_no_sources() as nuget_config:
|
||||
lang_base.setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
'dotnet', 'tool', 'install',
|
||||
'--configfile', nuget_config,
|
||||
'--tool-path', os.path.join(envdir, BIN_DIR),
|
||||
'--add-source', build_dir,
|
||||
tool_id,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.prefix import Prefix
|
||||
|
||||
ENVIRONMENT_DIR = None
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
install_environment = helpers.no_install
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
install_environment = lang_base.no_install
|
||||
in_env = lang_base.no_env
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
out = f'{hook.entry}\n\n'.encode()
|
||||
out = f'{entry}\n\n'.encode()
|
||||
out += b'\n'.join(f.encode() for f in file_args) + b'\n'
|
||||
return 1, out
|
||||
|
|
|
|||
|
|
@ -1,101 +1,161 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import json
|
||||
import os.path
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
import tarfile
|
||||
import tempfile
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
from typing import ContextManager
|
||||
from typing import IO
|
||||
from typing import Protocol
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import git
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.git import no_git_env
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import rmtree
|
||||
|
||||
ENVIRONMENT_DIR = 'golangenv'
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
_ARCH_ALIASES = {
|
||||
'x86_64': 'amd64',
|
||||
'i386': '386',
|
||||
'aarch64': 'arm64',
|
||||
'armv8': 'arm64',
|
||||
'armv7l': 'armv6l',
|
||||
}
|
||||
_ARCH = platform.machine().lower()
|
||||
_ARCH = _ARCH_ALIASES.get(_ARCH, _ARCH)
|
||||
|
||||
|
||||
def get_env_patch(venv: str) -> PatchesT:
|
||||
class ExtractAll(Protocol):
|
||||
def extractall(self, path: str) -> None: ...
|
||||
|
||||
|
||||
if sys.platform == 'win32': # pragma: win32 cover
|
||||
_EXT = 'zip'
|
||||
|
||||
def _open_archive(bio: IO[bytes]) -> ContextManager[ExtractAll]:
|
||||
return zipfile.ZipFile(bio)
|
||||
else: # pragma: win32 no cover
|
||||
_EXT = 'tar.gz'
|
||||
|
||||
def _open_archive(bio: IO[bytes]) -> ContextManager[ExtractAll]:
|
||||
return tarfile.open(fileobj=bio)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def get_default_version() -> str:
|
||||
if lang_base.exe_exists('go'):
|
||||
return 'system'
|
||||
else:
|
||||
return C.DEFAULT
|
||||
|
||||
|
||||
def get_env_patch(venv: str, version: str) -> PatchesT:
|
||||
if version == 'system':
|
||||
return (
|
||||
('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))),
|
||||
)
|
||||
|
||||
return (
|
||||
('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))),
|
||||
('GOROOT', os.path.join(venv, '.go')),
|
||||
('GOTOOLCHAIN', 'local'),
|
||||
(
|
||||
'PATH', (
|
||||
os.path.join(venv, 'bin'), os.pathsep,
|
||||
os.path.join(venv, '.go', 'bin'), os.pathsep, Var('PATH'),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def _infer_go_version(version: str) -> str:
|
||||
if version != C.DEFAULT:
|
||||
return version
|
||||
resp = urllib.request.urlopen('https://go.dev/dl/?mode=json')
|
||||
return json.load(resp)[0]['version'].removeprefix('go')
|
||||
|
||||
|
||||
def _get_url(version: str) -> str:
|
||||
os_name = platform.system().lower()
|
||||
version = _infer_go_version(version)
|
||||
return f'https://dl.google.com/go/go{version}.{os_name}-{_ARCH}.{_EXT}'
|
||||
|
||||
|
||||
def _install_go(version: str, dest: str) -> None:
|
||||
try:
|
||||
resp = urllib.request.urlopen(_get_url(version))
|
||||
except urllib.error.HTTPError as e: # pragma: no cover
|
||||
if e.code == 404:
|
||||
raise ValueError(
|
||||
f'Could not find a version matching your system requirements '
|
||||
f'(os={platform.system().lower()}; arch={_ARCH})',
|
||||
) from e
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
with tempfile.TemporaryFile() as f:
|
||||
shutil.copyfileobj(resp, f)
|
||||
f.seek(0)
|
||||
|
||||
with _open_archive(f) as archive:
|
||||
archive.extractall(dest)
|
||||
shutil.move(os.path.join(dest, 'go'), os.path.join(dest, '.go'))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(prefix: Prefix) -> Generator[None, None, None]:
|
||||
envdir = prefix.path(
|
||||
helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
|
||||
)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir, version)):
|
||||
yield
|
||||
|
||||
|
||||
def guess_go_dir(remote_url: str) -> str:
|
||||
if remote_url.endswith('.git'):
|
||||
remote_url = remote_url[:-1 * len('.git')]
|
||||
looks_like_url = (
|
||||
not remote_url.startswith('file://') and
|
||||
('//' in remote_url or '@' in remote_url)
|
||||
)
|
||||
remote_url = remote_url.replace(':', '/')
|
||||
if looks_like_url:
|
||||
_, _, remote_url = remote_url.rpartition('//')
|
||||
_, _, remote_url = remote_url.rpartition('@')
|
||||
return remote_url
|
||||
else:
|
||||
return 'unknown_src_dir'
|
||||
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
helpers.assert_version_default('golang', version)
|
||||
directory = prefix.path(
|
||||
helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
|
||||
)
|
||||
env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
with clean_path_on_failure(directory):
|
||||
remote = git.get_remote_url(prefix.prefix_dir)
|
||||
repo_src_dir = os.path.join(directory, 'src', guess_go_dir(remote))
|
||||
if version != 'system':
|
||||
_install_go(version, env_dir)
|
||||
|
||||
# Clone into the goenv we'll create
|
||||
cmd = ('git', 'clone', '--recursive', '.', repo_src_dir)
|
||||
helpers.run_setup_cmd(prefix, cmd)
|
||||
if sys.platform == 'cygwin': # pragma: no cover
|
||||
gopath = cmd_output('cygpath', '-w', env_dir)[1].strip()
|
||||
else:
|
||||
gopath = env_dir
|
||||
|
||||
if sys.platform == 'cygwin': # pragma: no cover
|
||||
_, gopath, _ = cmd_output('cygpath', '-w', directory)
|
||||
gopath = gopath.strip()
|
||||
else:
|
||||
gopath = directory
|
||||
env = dict(os.environ, GOPATH=gopath)
|
||||
env.pop('GOBIN', None)
|
||||
cmd_output_b('go', 'install', './...', cwd=repo_src_dir, env=env)
|
||||
for dependency in additional_dependencies:
|
||||
cmd_output_b(
|
||||
'go', 'install', dependency, cwd=repo_src_dir, env=env,
|
||||
)
|
||||
# Same some disk space, we don't need these after installation
|
||||
rmtree(prefix.path(directory, 'src'))
|
||||
pkgdir = prefix.path(directory, 'pkg')
|
||||
if os.path.exists(pkgdir): # pragma: no cover (go<1.10)
|
||||
rmtree(pkgdir)
|
||||
env = no_git_env(dict(os.environ, GOPATH=gopath))
|
||||
env.pop('GOBIN', None)
|
||||
if version != 'system':
|
||||
env['GOTOOLCHAIN'] = 'local'
|
||||
env['GOROOT'] = os.path.join(env_dir, '.go')
|
||||
env['PATH'] = os.pathsep.join((
|
||||
os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'],
|
||||
))
|
||||
|
||||
lang_base.setup_cmd(prefix, ('go', 'install', './...'), env=env)
|
||||
for dependency in additional_dependencies:
|
||||
lang_base.setup_cmd(prefix, ('go', 'install', dependency), env=env)
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
# save some disk space -- we don't need this after installation
|
||||
pkgdir = os.path.join(env_dir, 'pkg')
|
||||
if os.path.exists(pkgdir): # pragma: no branch (always true on windows?)
|
||||
rmtree(pkgdir)
|
||||
|
|
|
|||
56
pre_commit/languages/haskell.py
Normal file
56
pre_commit/languages/haskell.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os.path
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.errors import FatalError
|
||||
from pre_commit.prefix import Prefix
|
||||
|
||||
ENVIRONMENT_DIR = 'hs_env'
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def get_env_patch(target_dir: str) -> PatchesT:
|
||||
bin_path = os.path.join(target_dir, 'bin')
|
||||
return (('PATH', (bin_path, os.pathsep, Var('PATH'))),)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
lang_base.assert_version_default('haskell', version)
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
pkgs = [*prefix.star('.cabal'), *additional_dependencies]
|
||||
if not pkgs:
|
||||
raise FatalError('Expected .cabal files or additional_dependencies')
|
||||
|
||||
bindir = os.path.join(envdir, 'bin')
|
||||
os.makedirs(bindir, exist_ok=True)
|
||||
lang_base.setup_cmd(prefix, ('cabal', 'update'))
|
||||
lang_base.setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
'cabal', 'install',
|
||||
'--install-method', 'copy',
|
||||
'--installdir', bindir,
|
||||
*pkgs,
|
||||
),
|
||||
)
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import NoReturn
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import parse_shebang
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.xargs import xargs
|
||||
|
||||
FIXED_RANDOM_SEED = 1542676187
|
||||
|
||||
SHIMS_RE = re.compile(r'[/\\]shims[/\\]')
|
||||
|
||||
|
||||
def exe_exists(exe: str) -> bool:
|
||||
found = parse_shebang.find_executable(exe)
|
||||
if found is None: # exe exists
|
||||
return False
|
||||
|
||||
homedir = os.path.expanduser('~')
|
||||
try:
|
||||
common: str | None = os.path.commonpath((found, homedir))
|
||||
except ValueError: # on windows, different drives raises ValueError
|
||||
common = None
|
||||
|
||||
return (
|
||||
# it is not in a /shims/ directory
|
||||
not SHIMS_RE.search(found) and
|
||||
(
|
||||
# the homedir is / (docker, service user, etc.)
|
||||
os.path.dirname(homedir) == homedir or
|
||||
# the exe is not contained in the home directory
|
||||
common != homedir
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def run_setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None:
|
||||
cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs)
|
||||
|
||||
|
||||
@overload
|
||||
def environment_dir(d: None, language_version: str) -> None: ...
|
||||
@overload
|
||||
def environment_dir(d: str, language_version: str) -> str: ...
|
||||
|
||||
|
||||
def environment_dir(d: str | None, language_version: str) -> str | None:
|
||||
if d is None:
|
||||
return None
|
||||
else:
|
||||
return f'{d}-{language_version}'
|
||||
|
||||
|
||||
def assert_version_default(binary: str, version: str) -> None:
|
||||
if version != C.DEFAULT:
|
||||
raise AssertionError(
|
||||
f'for now, pre-commit requires system-installed {binary} -- '
|
||||
f'you selected `language_version: {version}`',
|
||||
)
|
||||
|
||||
|
||||
def assert_no_additional_deps(
|
||||
lang: str,
|
||||
additional_deps: Sequence[str],
|
||||
) -> None:
|
||||
if additional_deps:
|
||||
raise AssertionError(
|
||||
f'for now, pre-commit does not support '
|
||||
f'additional_dependencies for {lang} -- '
|
||||
f'you selected `additional_dependencies: {additional_deps}`',
|
||||
)
|
||||
|
||||
|
||||
def basic_get_default_version() -> str:
|
||||
return C.DEFAULT
|
||||
|
||||
|
||||
def basic_health_check(prefix: Prefix, language_version: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def no_install(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> NoReturn:
|
||||
raise AssertionError('This type is not installable')
|
||||
|
||||
|
||||
def target_concurrency(hook: Hook) -> int:
|
||||
if hook.require_serial or 'PRE_COMMIT_NO_CONCURRENCY' in os.environ:
|
||||
return 1
|
||||
else:
|
||||
# Travis appears to have a bunch of CPUs, but we can't use them all.
|
||||
if 'TRAVIS' in os.environ:
|
||||
return 2
|
||||
else:
|
||||
try:
|
||||
return multiprocessing.cpu_count()
|
||||
except NotImplementedError:
|
||||
return 1
|
||||
|
||||
|
||||
def _shuffled(seq: Sequence[str]) -> list[str]:
|
||||
"""Deterministically shuffle"""
|
||||
fixed_random = random.Random()
|
||||
fixed_random.seed(FIXED_RANDOM_SEED, version=1)
|
||||
|
||||
seq = list(seq)
|
||||
fixed_random.shuffle(seq)
|
||||
return seq
|
||||
|
||||
|
||||
def run_xargs(
|
||||
hook: Hook,
|
||||
cmd: tuple[str, ...],
|
||||
file_args: Sequence[str],
|
||||
**kwargs: Any,
|
||||
) -> tuple[int, bytes]:
|
||||
# Shuffle the files so that they more evenly fill out the xargs partitions,
|
||||
# but do it deterministically in case a hook cares about ordering.
|
||||
file_args = _shuffled(file_args)
|
||||
kwargs['target_concurrency'] = target_concurrency(hook)
|
||||
return xargs(cmd, file_args, **kwargs)
|
||||
133
pre_commit/languages/julia.py
Normal file
133
pre_commit/languages/julia.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import shutil
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import UNSET
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import cmd_output_b
|
||||
|
||||
ENVIRONMENT_DIR = 'juliaenv'
|
||||
health_check = lang_base.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
|
||||
|
||||
def run_hook(
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
# `entry` is a (hook-repo relative) file followed by (optional) args, e.g.
|
||||
# `bin/id.jl` or `bin/hook.jl --arg1 --arg2` so we
|
||||
# 1) shell parse it and join with args with hook_cmd
|
||||
# 2) prepend the hooks prefix path to the first argument (the file), unless
|
||||
# it is a local script
|
||||
# 3) prepend `julia` as the interpreter
|
||||
|
||||
cmd = lang_base.hook_cmd(entry, args)
|
||||
script = cmd[0] if is_local else prefix.path(cmd[0])
|
||||
cmd = ('julia', '--startup-file=no', script, *cmd[1:])
|
||||
return lang_base.run_xargs(
|
||||
cmd,
|
||||
file_args,
|
||||
require_serial=require_serial,
|
||||
color=color,
|
||||
)
|
||||
|
||||
|
||||
def get_env_patch(target_dir: str, version: str) -> PatchesT:
|
||||
return (
|
||||
('JULIA_LOAD_PATH', target_dir),
|
||||
# May be set, remove it to not interfer with LOAD_PATH
|
||||
('JULIA_PROJECT', UNSET),
|
||||
)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir, version)):
|
||||
yield
|
||||
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix,
|
||||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with in_env(prefix, version):
|
||||
# TODO: Support language_version with juliaup similar to rust via
|
||||
# rustup
|
||||
# if version != 'system':
|
||||
# ...
|
||||
|
||||
# Copy Project.toml to hook env if it exist
|
||||
os.makedirs(envdir, exist_ok=True)
|
||||
project_names = ('JuliaProject.toml', 'Project.toml')
|
||||
project_found = False
|
||||
for project_name in project_names:
|
||||
project_file = prefix.path(project_name)
|
||||
if not os.path.isfile(project_file):
|
||||
continue
|
||||
shutil.copy(project_file, envdir)
|
||||
project_found = True
|
||||
break
|
||||
|
||||
# If no project file was found we create an empty one so that the
|
||||
# package manager doesn't error
|
||||
if not project_found:
|
||||
open(os.path.join(envdir, 'Project.toml'), 'a').close()
|
||||
|
||||
# Copy Manifest.toml to hook env if it exists
|
||||
manifest_names = ('JuliaManifest.toml', 'Manifest.toml')
|
||||
for manifest_name in manifest_names:
|
||||
manifest_file = prefix.path(manifest_name)
|
||||
if not os.path.isfile(manifest_file):
|
||||
continue
|
||||
shutil.copy(manifest_file, envdir)
|
||||
break
|
||||
|
||||
# Julia code to instantiate the hook environment
|
||||
julia_code = """
|
||||
@assert length(ARGS) > 0
|
||||
hook_env = ARGS[1]
|
||||
deps = join(ARGS[2:end], " ")
|
||||
|
||||
# We prepend @stdlib here so that we can load the package manager even
|
||||
# though `get_env_patch` limits `JULIA_LOAD_PATH` to just the hook env.
|
||||
pushfirst!(LOAD_PATH, "@stdlib")
|
||||
using Pkg
|
||||
popfirst!(LOAD_PATH)
|
||||
|
||||
# Instantiate the environment shipped with the hook repo. If we have
|
||||
# additional dependencies we disable precompilation in this step to
|
||||
# avoid double work.
|
||||
precompile = isempty(deps) ? "1" : "0"
|
||||
withenv("JULIA_PKG_PRECOMPILE_AUTO" => precompile) do
|
||||
Pkg.instantiate()
|
||||
end
|
||||
|
||||
# Add additional dependencies (with precompilation)
|
||||
if !isempty(deps)
|
||||
withenv("JULIA_PKG_PRECOMPILE_AUTO" => "1") do
|
||||
Pkg.REPLMode.pkgstr("add " * deps)
|
||||
end
|
||||
end
|
||||
"""
|
||||
cmd_output_b(
|
||||
'julia', '--startup-file=no', '-e', julia_code, '--', envdir,
|
||||
*additional_dependencies,
|
||||
cwd=prefix.prefix_dir,
|
||||
)
|
||||
|
|
@ -3,22 +3,20 @@ from __future__ import annotations
|
|||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output
|
||||
|
||||
ENVIRONMENT_DIR = 'lua_env'
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def _get_lua_version() -> str: # pragma: win32 no cover
|
||||
|
|
@ -45,14 +43,10 @@ def get_env_patch(d: str) -> PatchesT: # pragma: win32 no cover
|
|||
)
|
||||
|
||||
|
||||
def _envdir(prefix: Prefix) -> str: # pragma: win32 no cover
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT)
|
||||
return prefix.path(directory)
|
||||
|
||||
|
||||
@contextlib.contextmanager # pragma: win32 no cover
|
||||
def in_env(prefix: Prefix) -> Generator[None, None, None]:
|
||||
with envcontext(get_env_patch(_envdir(prefix))):
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
|
|
@ -61,31 +55,21 @@ def install_environment(
|
|||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None: # pragma: win32 no cover
|
||||
helpers.assert_version_default('lua', version)
|
||||
lang_base.assert_version_default('lua', version)
|
||||
|
||||
envdir = _envdir(prefix)
|
||||
with clean_path_on_failure(envdir):
|
||||
with in_env(prefix):
|
||||
# luarocks doesn't bootstrap a tree prior to installing
|
||||
# so ensure the directory exists.
|
||||
os.makedirs(envdir, exist_ok=True)
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with in_env(prefix, version):
|
||||
# luarocks doesn't bootstrap a tree prior to installing
|
||||
# so ensure the directory exists.
|
||||
os.makedirs(envdir, exist_ok=True)
|
||||
|
||||
# Older luarocks (e.g., 2.4.2) expect the rockspec as an arg
|
||||
for rockspec in prefix.star('.rockspec'):
|
||||
make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec)
|
||||
helpers.run_setup_cmd(prefix, make_cmd)
|
||||
# Older luarocks (e.g., 2.4.2) expect the rockspec as an arg
|
||||
for rockspec in prefix.star('.rockspec'):
|
||||
make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec)
|
||||
lang_base.setup_cmd(prefix, make_cmd)
|
||||
|
||||
# luarocks can't install multiple packages at once
|
||||
# so install them individually.
|
||||
for dependency in additional_dependencies:
|
||||
cmd = ('luarocks', '--tree', envdir, 'install', dependency)
|
||||
helpers.run_setup_cmd(prefix, cmd)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]: # pragma: win32 no cover
|
||||
with in_env(hook.prefix):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
# luarocks can't install multiple packages at once
|
||||
# so install them individually.
|
||||
for dependency in additional_dependencies:
|
||||
cmd = ('luarocks', '--tree', envdir, 'install', dependency)
|
||||
lang_base.setup_cmd(prefix, cmd)
|
||||
|
|
|
|||
|
|
@ -4,24 +4,23 @@ import contextlib
|
|||
import functools
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import UNSET
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.languages.python import bin_dir
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import rmtree
|
||||
|
||||
ENVIRONMENT_DIR = 'node_env'
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
|
|
@ -31,17 +30,12 @@ def get_default_version() -> str:
|
|||
return C.DEFAULT
|
||||
# if node is already installed, we can save a bunch of setup time by
|
||||
# using the installed version
|
||||
elif all(helpers.exe_exists(exe) for exe in ('node', 'npm')):
|
||||
elif all(lang_base.exe_exists(exe) for exe in ('node', 'npm')):
|
||||
return 'system'
|
||||
else:
|
||||
return C.DEFAULT
|
||||
|
||||
|
||||
def _envdir(prefix: Prefix, version: str) -> str:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
|
||||
return prefix.path(directory)
|
||||
|
||||
|
||||
def get_env_patch(venv: str) -> PatchesT:
|
||||
if sys.platform == 'cygwin': # pragma: no cover
|
||||
_, win_venv, _ = cmd_output('cygpath', '-w', venv)
|
||||
|
|
@ -65,16 +59,14 @@ def get_env_patch(venv: str) -> PatchesT:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
with envcontext(get_env_patch(_envdir(prefix, language_version))):
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
def health_check(prefix: Prefix, language_version: str) -> str | None:
|
||||
with in_env(prefix, language_version):
|
||||
def health_check(prefix: Prefix, version: str) -> str | None:
|
||||
with in_env(prefix, version):
|
||||
retcode, _, _ = cmd_output_b('node', '--version', check=False)
|
||||
if retcode != 0: # pragma: win32 no cover
|
||||
return f'`node --version` returned {retcode}'
|
||||
|
|
@ -85,47 +77,34 @@ def health_check(prefix: Prefix, language_version: str) -> str | None:
|
|||
def install_environment(
|
||||
prefix: Prefix, version: str, additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
additional_dependencies = tuple(additional_dependencies)
|
||||
assert prefix.exists('package.json')
|
||||
envdir = _envdir(prefix, version)
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath
|
||||
if sys.platform == 'win32': # pragma: no cover
|
||||
envdir = fr'\\?\{os.path.normpath(envdir)}'
|
||||
with clean_path_on_failure(envdir):
|
||||
cmd = [
|
||||
sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir,
|
||||
]
|
||||
if version != C.DEFAULT:
|
||||
cmd.extend(['-n', version])
|
||||
cmd_output_b(*cmd)
|
||||
cmd = [sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir]
|
||||
if version != C.DEFAULT:
|
||||
cmd.extend(['-n', version])
|
||||
cmd_output_b(*cmd)
|
||||
|
||||
with in_env(prefix, version):
|
||||
# https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449
|
||||
# install as if we installed from git
|
||||
with in_env(prefix, version):
|
||||
# https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449
|
||||
# install as if we installed from git
|
||||
|
||||
local_install_cmd = (
|
||||
'npm', 'install', '--dev', '--prod',
|
||||
'--ignore-prepublish', '--no-progress', '--no-save',
|
||||
)
|
||||
helpers.run_setup_cmd(prefix, local_install_cmd)
|
||||
local_install_cmd = (
|
||||
'npm', 'install', '--include=dev', '--include=prod',
|
||||
'--ignore-prepublish', '--no-progress', '--no-save',
|
||||
)
|
||||
lang_base.setup_cmd(prefix, local_install_cmd)
|
||||
|
||||
_, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir)
|
||||
pkg = prefix.path(pkg.strip())
|
||||
_, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir)
|
||||
pkg = prefix.path(pkg.strip())
|
||||
|
||||
install = ('npm', 'install', '-g', pkg, *additional_dependencies)
|
||||
helpers.run_setup_cmd(prefix, install)
|
||||
install = ('npm', 'install', '-g', pkg, *additional_dependencies)
|
||||
lang_base.setup_cmd(prefix, install)
|
||||
|
||||
# clean these up after installation
|
||||
if prefix.exists('node_modules'): # pragma: win32 no cover
|
||||
rmtree(prefix.path('node_modules'))
|
||||
os.remove(pkg)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
# clean these up after installation
|
||||
if prefix.exists('node_modules'): # pragma: win32 no cover
|
||||
rmtree(prefix.path('node_modules'))
|
||||
os.remove(pkg)
|
||||
|
|
|
|||
|
|
@ -3,25 +3,19 @@ from __future__ import annotations
|
|||
import contextlib
|
||||
import os
|
||||
import shlex
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
|
||||
ENVIRONMENT_DIR = 'perl_env'
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
|
||||
|
||||
def _envdir(prefix: Prefix, version: str) -> str:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
|
||||
return prefix.path(directory)
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def get_env_patch(venv: str) -> PatchesT:
|
||||
|
|
@ -39,30 +33,18 @@ def get_env_patch(venv: str) -> PatchesT:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
with envcontext(get_env_patch(_envdir(prefix, language_version))):
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix, version: str, additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
helpers.assert_version_default('perl', version)
|
||||
lang_base.assert_version_default('perl', version)
|
||||
|
||||
with clean_path_on_failure(_envdir(prefix, version)):
|
||||
with in_env(prefix, version):
|
||||
helpers.run_setup_cmd(
|
||||
prefix, ('cpan', '-T', '.', *additional_dependencies),
|
||||
)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
with in_env(prefix, version):
|
||||
lang_base.setup_cmd(
|
||||
prefix, ('cpan', '-T', '.', *additional_dependencies),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,19 +3,20 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from re import Pattern
|
||||
from typing import NamedTuple
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit import output
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.xargs import xargs
|
||||
|
||||
ENVIRONMENT_DIR = None
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
install_environment = helpers.no_install
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
install_environment = lang_base.no_install
|
||||
in_env = lang_base.no_env
|
||||
|
||||
|
||||
def _process_filename_by_line(pattern: Pattern[bytes], filename: str) -> int:
|
||||
|
|
@ -87,12 +88,17 @@ FNS = {
|
|||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
exe = (sys.executable, '-m', __name__) + tuple(hook.args) + (hook.entry,)
|
||||
return xargs(exe, file_args, color=color)
|
||||
cmd = (sys.executable, '-m', __name__, *args, entry)
|
||||
return xargs(cmd, file_args, color=color)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
|
|
|
|||
|
|
@ -4,28 +4,27 @@ import contextlib
|
|||
import functools
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import UNSET
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.parse_shebang import find_executable
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import CalledProcessError
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import win_exe
|
||||
|
||||
ENVIRONMENT_DIR = 'py_env'
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
@functools.cache
|
||||
def _version_info(exe: str) -> str:
|
||||
prog = 'import sys;print(".".join(str(p) for p in sys.version_info))'
|
||||
try:
|
||||
|
|
@ -49,7 +48,7 @@ def _read_pyvenv_cfg(filename: str) -> dict[str, str]:
|
|||
|
||||
def bin_dir(venv: str) -> str:
|
||||
"""On windows there's a different directory for the virtualenv"""
|
||||
bin_part = 'Scripts' if os.name == 'nt' else 'bin'
|
||||
bin_part = 'Scripts' if sys.platform == 'win32' else 'bin'
|
||||
return os.path.join(venv, bin_part)
|
||||
|
||||
|
||||
|
|
@ -66,7 +65,7 @@ def _find_by_py_launcher(
|
|||
version: str,
|
||||
) -> str | None: # pragma: no cover (windows only)
|
||||
if version.startswith('python'):
|
||||
num = version[len('python'):]
|
||||
num = version.removeprefix('python')
|
||||
cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)')
|
||||
env = dict(os.environ, PYTHONIOENCODING='UTF-8')
|
||||
try:
|
||||
|
|
@ -76,6 +75,13 @@ def _find_by_py_launcher(
|
|||
return None
|
||||
|
||||
|
||||
def _impl_exe_name() -> str:
|
||||
if sys.implementation.name == 'cpython': # pragma: cpython cover
|
||||
return 'python'
|
||||
else: # pragma: cpython no cover
|
||||
return sys.implementation.name # pypy mostly
|
||||
|
||||
|
||||
def _find_by_sys_executable() -> str | None:
|
||||
def _norm(path: str) -> str | None:
|
||||
_, exe = os.path.split(path.lower())
|
||||
|
|
@ -101,18 +107,25 @@ def _find_by_sys_executable() -> str | None:
|
|||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def get_default_version() -> str: # pragma: no cover (platform dependent)
|
||||
# First attempt from `sys.executable` (or the realpath)
|
||||
exe = _find_by_sys_executable()
|
||||
if exe:
|
||||
return exe
|
||||
v_major = f'{sys.version_info[0]}'
|
||||
v_minor = f'{sys.version_info[0]}.{sys.version_info[1]}'
|
||||
|
||||
# Next try the `pythonX.X` executable
|
||||
exe = f'python{sys.version_info[0]}.{sys.version_info[1]}'
|
||||
if find_executable(exe):
|
||||
return exe
|
||||
# attempt the likely implementation exe
|
||||
for potential in (v_minor, v_major):
|
||||
exe = f'{_impl_exe_name()}{potential}'
|
||||
if find_executable(exe):
|
||||
return exe
|
||||
|
||||
if _find_by_py_launcher(exe):
|
||||
return exe
|
||||
# next try `sys.executable` (or the realpath)
|
||||
maybe_exe = _find_by_sys_executable()
|
||||
if maybe_exe:
|
||||
return maybe_exe
|
||||
|
||||
# maybe on windows we can find it via py launcher?
|
||||
if sys.platform == 'win32': # pragma: win32 cover
|
||||
exe = f'python{v_minor}'
|
||||
if _find_by_py_launcher(exe):
|
||||
return exe
|
||||
|
||||
# We tried!
|
||||
return C.DEFAULT
|
||||
|
|
@ -125,7 +138,7 @@ def _sys_executable_matches(version: str) -> bool:
|
|||
return False
|
||||
|
||||
try:
|
||||
info = tuple(int(p) for p in version[len('python'):].split('.'))
|
||||
info = tuple(int(p) for p in version.removeprefix('python').split('.'))
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
|
@ -138,7 +151,7 @@ def norm_version(version: str) -> str | None:
|
|||
elif _sys_executable_matches(version): # virtualenv defaults to our exe
|
||||
return None
|
||||
|
||||
if os.name == 'nt': # pragma: no cover (windows)
|
||||
if sys.platform == 'win32': # pragma: no cover (windows)
|
||||
version_exec = _find_by_py_launcher(version)
|
||||
if version_exec:
|
||||
return version_exec
|
||||
|
|
@ -153,19 +166,14 @@ def norm_version(version: str) -> str | None:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version)
|
||||
envdir = prefix.path(directory)
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
def health_check(prefix: Prefix, language_version: str) -> str | None:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version)
|
||||
envdir = prefix.path(directory)
|
||||
def health_check(prefix: Prefix, version: str) -> str | None:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg')
|
||||
|
||||
# created with "old" virtualenv
|
||||
|
|
@ -208,23 +216,13 @@ def install_environment(
|
|||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
venv_cmd = [sys.executable, '-mvirtualenv', envdir]
|
||||
python = norm_version(version)
|
||||
if python is not None:
|
||||
venv_cmd.extend(('-p', python))
|
||||
install_cmd = ('python', '-mpip', 'install', '.', *additional_dependencies)
|
||||
|
||||
with clean_path_on_failure(envdir):
|
||||
cmd_output_b(*venv_cmd, cwd='/')
|
||||
with in_env(prefix, version):
|
||||
helpers.run_setup_cmd(prefix, install_cmd)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
cmd_output_b(*venv_cmd, cwd='/')
|
||||
with in_env(prefix, version):
|
||||
lang_base.setup_cmd(prefix, install_cmd)
|
||||
|
|
|
|||
|
|
@ -4,23 +4,120 @@ import contextlib
|
|||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
import tempfile
|
||||
import textwrap
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import UNSET
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import win_exe
|
||||
|
||||
ENVIRONMENT_DIR = 'renv'
|
||||
RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ')
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
|
||||
_RENV_ACTIVATED_OPTS = (
|
||||
'--no-save', '--no-restore', '--no-site-file', '--no-environ',
|
||||
)
|
||||
|
||||
|
||||
def _execute_r(
|
||||
code: str, *,
|
||||
prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
|
||||
cli_opts: Sequence[str],
|
||||
) -> str:
|
||||
with in_env(prefix, version), _r_code_in_tempfile(code) as f:
|
||||
_, out, _ = cmd_output(
|
||||
_rscript_exec(), *cli_opts, f, *args, cwd=cwd,
|
||||
)
|
||||
return out.rstrip('\n')
|
||||
|
||||
|
||||
def _execute_r_in_renv(
|
||||
code: str, *,
|
||||
prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
|
||||
) -> str:
|
||||
return _execute_r(
|
||||
code=code, prefix=prefix, version=version, args=args, cwd=cwd,
|
||||
cli_opts=_RENV_ACTIVATED_OPTS,
|
||||
)
|
||||
|
||||
|
||||
def _execute_vanilla_r(
|
||||
code: str, *,
|
||||
prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
|
||||
) -> str:
|
||||
return _execute_r(
|
||||
code=code, prefix=prefix, version=version, args=args, cwd=cwd,
|
||||
cli_opts=('--vanilla',),
|
||||
)
|
||||
|
||||
|
||||
def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str:
|
||||
return _execute_r_in_renv(
|
||||
'cat(renv::settings$r.version())',
|
||||
prefix=prefix, version=version,
|
||||
cwd=envdir,
|
||||
)
|
||||
|
||||
|
||||
def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str:
|
||||
return _execute_r_in_renv(
|
||||
'cat(as.character(getRversion()))',
|
||||
prefix=prefix, version=version,
|
||||
cwd=envdir,
|
||||
)
|
||||
|
||||
|
||||
def _write_current_r_version(
|
||||
envdir: str, prefix: Prefix, version: str,
|
||||
) -> None:
|
||||
_execute_r_in_renv(
|
||||
'renv::settings$r.version(as.character(getRversion()))',
|
||||
prefix=prefix, version=version,
|
||||
cwd=envdir,
|
||||
)
|
||||
|
||||
|
||||
def health_check(prefix: Prefix, version: str) -> str | None:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
r_version_installation = _read_installed_version(
|
||||
envdir=envdir, prefix=prefix, version=version,
|
||||
)
|
||||
r_version_current_executable = _read_executable_version(
|
||||
envdir=envdir, prefix=prefix, version=version,
|
||||
)
|
||||
if r_version_installation in {'NULL', ''}:
|
||||
return (
|
||||
f'Hooks were installed with an unknown R version. R version for '
|
||||
f'hook repo now set to {r_version_current_executable}'
|
||||
)
|
||||
elif r_version_installation != r_version_current_executable:
|
||||
return (
|
||||
f'Hooks were installed for R version {r_version_installation}, '
|
||||
f'but current R executable has version '
|
||||
f'{r_version_current_executable}'
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _r_code_in_tempfile(code: str) -> Generator[str]:
|
||||
"""
|
||||
To avoid quoting and escaping issues, avoid `Rscript [options] -e {expr}`
|
||||
but use `Rscript [options] path/to/file_with_expr.R`
|
||||
"""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
fname = os.path.join(tmpdir, 'script.R')
|
||||
with open(fname, 'w') as f:
|
||||
f.write(_inline_r_setup(textwrap.dedent(code)))
|
||||
yield fname
|
||||
|
||||
|
||||
def get_env_patch(venv: str) -> PatchesT:
|
||||
|
|
@ -31,32 +128,22 @@ def get_env_patch(venv: str) -> PatchesT:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
envdir = _get_env_dir(prefix, language_version)
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
||||
def _get_env_dir(prefix: Prefix, version: str) -> str:
|
||||
return prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
|
||||
|
||||
|
||||
def _prefix_if_non_local_file_entry(
|
||||
entry: Sequence[str],
|
||||
prefix: Prefix,
|
||||
src: str,
|
||||
def _prefix_if_file_entry(
|
||||
entry: list[str],
|
||||
prefix: Prefix,
|
||||
*,
|
||||
is_local: bool,
|
||||
) -> Sequence[str]:
|
||||
if entry[1] == '-e':
|
||||
if entry[1] == '-e' or is_local:
|
||||
return entry[1:]
|
||||
else:
|
||||
if src == 'local':
|
||||
path = entry[1]
|
||||
else:
|
||||
path = prefix.path(entry[1])
|
||||
return (path,)
|
||||
return (prefix.path(entry[1]),)
|
||||
|
||||
|
||||
def _rscript_exec() -> str:
|
||||
|
|
@ -67,7 +154,7 @@ def _rscript_exec() -> str:
|
|||
return os.path.join(r_home, 'bin', win_exe('Rscript'))
|
||||
|
||||
|
||||
def _entry_validate(entry: Sequence[str]) -> None:
|
||||
def _entry_validate(entry: list[str]) -> None:
|
||||
"""
|
||||
Allowed entries:
|
||||
# Rscript -e expr
|
||||
|
|
@ -81,20 +168,23 @@ def _entry_validate(entry: Sequence[str]) -> None:
|
|||
raise ValueError('You can supply at most one expression.')
|
||||
elif len(entry) > 2:
|
||||
raise ValueError(
|
||||
'The only valid syntax is `Rscript -e {expr}`',
|
||||
'The only valid syntax is `Rscript -e {expr}`'
|
||||
'or `Rscript path/to/hook/script`',
|
||||
)
|
||||
|
||||
|
||||
def _cmd_from_hook(hook: Hook) -> tuple[str, ...]:
|
||||
entry = shlex.split(hook.entry)
|
||||
_entry_validate(entry)
|
||||
def _cmd_from_hook(
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
) -> tuple[str, ...]:
|
||||
cmd = shlex.split(entry)
|
||||
_entry_validate(cmd)
|
||||
|
||||
return (
|
||||
*entry[:1], *RSCRIPT_OPTS,
|
||||
*_prefix_if_non_local_file_entry(entry, hook.prefix, hook.src),
|
||||
*hook.args,
|
||||
)
|
||||
cmd_part = _prefix_if_file_entry(cmd, prefix, is_local=is_local)
|
||||
return (cmd[0], *_RENV_ACTIVATED_OPTS, *cmd_part, *args)
|
||||
|
||||
|
||||
def install_environment(
|
||||
|
|
@ -102,55 +192,54 @@ def install_environment(
|
|||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
env_dir = _get_env_dir(prefix, version)
|
||||
with clean_path_on_failure(env_dir):
|
||||
os.makedirs(env_dir, exist_ok=True)
|
||||
shutil.copy(prefix.path('renv.lock'), env_dir)
|
||||
shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))
|
||||
lang_base.assert_version_default('r', version)
|
||||
|
||||
r_code_inst_environment = f"""\
|
||||
prefix_dir <- {prefix.prefix_dir!r}
|
||||
options(
|
||||
repos = c(CRAN = "https://cran.rstudio.com"),
|
||||
renv.consent = TRUE
|
||||
)
|
||||
source("renv/activate.R")
|
||||
renv::restore()
|
||||
activate_statement <- paste0(
|
||||
'suppressWarnings({{',
|
||||
'old <- setwd("', getwd(), '"); ',
|
||||
'source("renv/activate.R"); ',
|
||||
'setwd(old); ',
|
||||
'renv::load("', getwd(), '");}})'
|
||||
)
|
||||
writeLines(activate_statement, 'activate.R')
|
||||
is_package <- tryCatch(
|
||||
{{
|
||||
path_desc <- file.path(prefix_dir, 'DESCRIPTION')
|
||||
suppressWarnings(desc <- read.dcf(path_desc))
|
||||
"Package" %in% colnames(desc)
|
||||
}},
|
||||
error = function(...) FALSE
|
||||
)
|
||||
if (is_package) {{
|
||||
renv::install(prefix_dir)
|
||||
}}
|
||||
"""
|
||||
env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
os.makedirs(env_dir, exist_ok=True)
|
||||
shutil.copy(prefix.path('renv.lock'), env_dir)
|
||||
shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))
|
||||
|
||||
cmd_output_b(
|
||||
_rscript_exec(), '--vanilla', '-e',
|
||||
_inline_r_setup(r_code_inst_environment),
|
||||
r_code_inst_environment = f"""\
|
||||
prefix_dir <- {prefix.prefix_dir!r}
|
||||
options(
|
||||
repos = c(CRAN = "https://cran.rstudio.com"),
|
||||
renv.consent = TRUE
|
||||
)
|
||||
source("renv/activate.R")
|
||||
renv::restore()
|
||||
activate_statement <- paste0(
|
||||
'suppressWarnings({{',
|
||||
'old <- setwd("', getwd(), '"); ',
|
||||
'source("renv/activate.R"); ',
|
||||
'setwd(old); ',
|
||||
'renv::load("', getwd(), '");}})'
|
||||
)
|
||||
writeLines(activate_statement, 'activate.R')
|
||||
is_package <- tryCatch(
|
||||
{{
|
||||
path_desc <- file.path(prefix_dir, 'DESCRIPTION')
|
||||
suppressWarnings(desc <- read.dcf(path_desc))
|
||||
"Package" %in% colnames(desc)
|
||||
}},
|
||||
error = function(...) FALSE
|
||||
)
|
||||
if (is_package) {{
|
||||
renv::install(prefix_dir)
|
||||
}}
|
||||
"""
|
||||
_execute_vanilla_r(
|
||||
r_code_inst_environment,
|
||||
prefix=prefix, version=version, cwd=env_dir,
|
||||
)
|
||||
|
||||
_write_current_r_version(envdir=env_dir, prefix=prefix, version=version)
|
||||
if additional_dependencies:
|
||||
r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
|
||||
_execute_r_in_renv(
|
||||
code=r_code_inst_add, prefix=prefix, version=version,
|
||||
args=additional_dependencies,
|
||||
cwd=env_dir,
|
||||
)
|
||||
if additional_dependencies:
|
||||
r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
|
||||
with in_env(prefix, version):
|
||||
cmd_output_b(
|
||||
_rscript_exec(), *RSCRIPT_OPTS, '-e',
|
||||
_inline_r_setup(r_code_inst_add),
|
||||
*additional_dependencies,
|
||||
cwd=env_dir,
|
||||
)
|
||||
|
||||
|
||||
def _inline_r_setup(code: str) -> str:
|
||||
|
|
@ -158,19 +247,32 @@ def _inline_r_setup(code: str) -> str:
|
|||
Some behaviour of R cannot be configured via env variables, but can
|
||||
only be configured via R options once R has started. These are set here.
|
||||
"""
|
||||
with_option = f"""\
|
||||
options(install.packages.compile.from.source = "never", pkgType = "binary")
|
||||
{code}
|
||||
"""
|
||||
return with_option
|
||||
with_option = [
|
||||
textwrap.dedent("""\
|
||||
options(
|
||||
install.packages.compile.from.source = "never",
|
||||
pkgType = "binary"
|
||||
)
|
||||
"""),
|
||||
code,
|
||||
]
|
||||
return '\n'.join(with_option)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(
|
||||
hook, _cmd_from_hook(hook), file_args, color=color,
|
||||
)
|
||||
cmd = _cmd_from_hook(prefix, entry, args, is_local=is_local)
|
||||
return lang_base.run_xargs(
|
||||
cmd,
|
||||
file_args,
|
||||
require_serial=require_serial,
|
||||
color=color,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,31 +2,36 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import functools
|
||||
import importlib.resources
|
||||
import os.path
|
||||
import shutil
|
||||
import tarfile
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
from typing import IO
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import UNSET
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import CalledProcessError
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import resource_bytesio
|
||||
|
||||
ENVIRONMENT_DIR = 'rbenv'
|
||||
health_check = helpers.basic_health_check
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def _resource_bytesio(filename: str) -> IO[bytes]:
|
||||
files = importlib.resources.files('pre_commit.resources')
|
||||
return files.joinpath(filename).open('rb')
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def get_default_version() -> str:
|
||||
if all(helpers.exe_exists(exe) for exe in ('ruby', 'gem')):
|
||||
if all(lang_base.exe_exists(exe) for exe in ('ruby', 'gem')):
|
||||
return 'system'
|
||||
else:
|
||||
return C.DEFAULT
|
||||
|
|
@ -68,19 +73,14 @@ def get_env_patch(
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
envdir = prefix.path(
|
||||
helpers.environment_dir(ENVIRONMENT_DIR, language_version),
|
||||
)
|
||||
with envcontext(get_env_patch(envdir, language_version)):
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir, version)):
|
||||
yield
|
||||
|
||||
|
||||
def _extract_resource(filename: str, dest: str) -> None:
|
||||
with resource_bytesio(filename) as bio:
|
||||
with _resource_bytesio(filename) as bio:
|
||||
with tarfile.open(fileobj=bio) as tf:
|
||||
tf.extractall(dest)
|
||||
|
||||
|
|
@ -89,14 +89,14 @@ def _install_rbenv(
|
|||
prefix: Prefix,
|
||||
version: str,
|
||||
) -> None: # pragma: win32 no cover
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
_extract_resource('rbenv.tar.gz', prefix.path('.'))
|
||||
shutil.move(prefix.path('rbenv'), prefix.path(directory))
|
||||
shutil.move(prefix.path('rbenv'), envdir)
|
||||
|
||||
# Only install ruby-build if the version is specified
|
||||
if version != C.DEFAULT:
|
||||
plugins_dir = prefix.path(directory, 'plugins')
|
||||
plugins_dir = os.path.join(envdir, 'plugins')
|
||||
_extract_resource('ruby-download.tar.gz', plugins_dir)
|
||||
_extract_resource('ruby-build.tar.gz', plugins_dir)
|
||||
|
||||
|
|
@ -106,48 +106,40 @@ def _install_ruby(
|
|||
version: str,
|
||||
) -> None: # pragma: win32 no cover
|
||||
try:
|
||||
helpers.run_setup_cmd(prefix, ('rbenv', 'download', version))
|
||||
lang_base.setup_cmd(prefix, ('rbenv', 'download', version))
|
||||
except CalledProcessError: # pragma: no cover (usually find with download)
|
||||
# Failed to download from mirror for some reason, build it instead
|
||||
helpers.run_setup_cmd(prefix, ('rbenv', 'install', version))
|
||||
lang_base.setup_cmd(prefix, ('rbenv', 'install', version))
|
||||
|
||||
|
||||
def install_environment(
|
||||
prefix: Prefix, version: str, additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
additional_dependencies = tuple(additional_dependencies)
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
|
||||
with clean_path_on_failure(prefix.path(directory)):
|
||||
if version != 'system': # pragma: win32 no cover
|
||||
_install_rbenv(prefix, version)
|
||||
with in_env(prefix, version):
|
||||
# Need to call this before installing so rbenv's directories
|
||||
# are set up
|
||||
helpers.run_setup_cmd(prefix, ('rbenv', 'init', '-'))
|
||||
if version != C.DEFAULT:
|
||||
_install_ruby(prefix, version)
|
||||
# Need to call this after installing to set up the shims
|
||||
helpers.run_setup_cmd(prefix, ('rbenv', 'rehash'))
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
if version != 'system': # pragma: win32 no cover
|
||||
_install_rbenv(prefix, version)
|
||||
with in_env(prefix, version):
|
||||
helpers.run_setup_cmd(
|
||||
prefix, ('gem', 'build', *prefix.star('.gemspec')),
|
||||
)
|
||||
helpers.run_setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
'gem', 'install',
|
||||
'--no-document', '--no-format-executable',
|
||||
'--no-user-install',
|
||||
*prefix.star('.gem'), *additional_dependencies,
|
||||
),
|
||||
)
|
||||
# Need to call this before installing so rbenv's directories
|
||||
# are set up
|
||||
lang_base.setup_cmd(prefix, ('rbenv', 'init', '-'))
|
||||
if version != C.DEFAULT:
|
||||
_install_ruby(prefix, version)
|
||||
# Need to call this after installing to set up the shims
|
||||
lang_base.setup_cmd(prefix, ('rbenv', 'rehash'))
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
with in_env(prefix, version):
|
||||
lang_base.setup_cmd(
|
||||
prefix, ('gem', 'build', *prefix.star('.gemspec')),
|
||||
)
|
||||
lang_base.setup_cmd(
|
||||
prefix,
|
||||
(
|
||||
'gem', 'install',
|
||||
'--no-document', '--no-format-executable',
|
||||
'--no-user-install',
|
||||
'--install-dir', os.path.join(envdir, 'gems'),
|
||||
'--bindir', os.path.join(envdir, 'gems', 'bin'),
|
||||
*prefix.star('.gem'), *additional_dependencies,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,24 +7,23 @@ import shutil
|
|||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit import parse_shebang
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import make_executable
|
||||
from pre_commit.util import win_exe
|
||||
|
||||
ENVIRONMENT_DIR = 'rustenv'
|
||||
health_check = helpers.basic_health_check
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
|
|
@ -35,7 +34,7 @@ def get_default_version() -> str:
|
|||
# Just detecting the executable does not suffice, because if rustup is
|
||||
# installed but no toolchain is available, then `cargo` exists but
|
||||
# cannot be used without installing a toolchain first.
|
||||
if cmd_output_b('cargo', '--version', check=False)[0] == 0:
|
||||
if cmd_output_b('cargo', '--version', check=False, cwd='/')[0] == 0:
|
||||
return 'system'
|
||||
else:
|
||||
return C.DEFAULT
|
||||
|
|
@ -49,14 +48,8 @@ def _rust_toolchain(language_version: str) -> str:
|
|||
return language_version
|
||||
|
||||
|
||||
def _envdir(prefix: Prefix, version: str) -> str:
|
||||
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
|
||||
return prefix.path(directory)
|
||||
|
||||
|
||||
def get_env_patch(target_dir: str, version: str) -> PatchesT:
|
||||
return (
|
||||
('CARGO_HOME', target_dir),
|
||||
('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))),
|
||||
# Only set RUSTUP_TOOLCHAIN if we don't want use the system's default
|
||||
# toolchain
|
||||
|
|
@ -68,13 +61,9 @@ def get_env_patch(target_dir: str, version: str) -> PatchesT:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_env(
|
||||
prefix: Prefix,
|
||||
language_version: str,
|
||||
) -> Generator[None, None, None]:
|
||||
with envcontext(
|
||||
get_env_patch(_envdir(prefix, language_version), language_version),
|
||||
):
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir, version)):
|
||||
yield
|
||||
|
||||
|
||||
|
|
@ -88,12 +77,12 @@ def _add_dependencies(
|
|||
crate = f'{name}@{spec or "*"}'
|
||||
crates.append(crate)
|
||||
|
||||
helpers.run_setup_cmd(prefix, ('cargo', 'add', *crates))
|
||||
lang_base.setup_cmd(prefix, ('cargo', 'add', *crates))
|
||||
|
||||
|
||||
def install_rust_with_toolchain(toolchain: str) -> None:
|
||||
def install_rust_with_toolchain(toolchain: str, envdir: str) -> None:
|
||||
with tempfile.TemporaryDirectory() as rustup_dir:
|
||||
with envcontext((('RUSTUP_HOME', rustup_dir),)):
|
||||
with envcontext((('CARGO_HOME', envdir), ('RUSTUP_HOME', rustup_dir))):
|
||||
# acquire `rustup` if not present
|
||||
if parse_shebang.find_executable('rustup') is None:
|
||||
# We did not detect rustup and need to download it first.
|
||||
|
|
@ -126,7 +115,7 @@ def install_environment(
|
|||
version: str,
|
||||
additional_dependencies: Sequence[str],
|
||||
) -> None:
|
||||
directory = _envdir(prefix, version)
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
# There are two cases where we might want to specify more dependencies:
|
||||
# as dependencies for the library being built, and as binary packages
|
||||
|
|
@ -143,34 +132,29 @@ def install_environment(
|
|||
}
|
||||
lib_deps = set(additional_dependencies) - cli_deps
|
||||
|
||||
with clean_path_on_failure(directory):
|
||||
packages_to_install: set[tuple[str, ...]] = {('--path', '.')}
|
||||
for cli_dep in cli_deps:
|
||||
cli_dep = cli_dep[len('cli:'):]
|
||||
package, _, crate_version = cli_dep.partition(':')
|
||||
if crate_version != '':
|
||||
packages_to_install.add((package, '--version', crate_version))
|
||||
else:
|
||||
packages_to_install.add((package,))
|
||||
packages_to_install: set[tuple[str, ...]] = {('--path', '.')}
|
||||
for cli_dep in cli_deps:
|
||||
cli_dep = cli_dep.removeprefix('cli:')
|
||||
package, _, crate_version = cli_dep.partition(':')
|
||||
if crate_version != '':
|
||||
packages_to_install.add((package, '--version', crate_version))
|
||||
else:
|
||||
packages_to_install.add((package,))
|
||||
|
||||
with in_env(prefix, version):
|
||||
if version != 'system':
|
||||
install_rust_with_toolchain(_rust_toolchain(version))
|
||||
with contextlib.ExitStack() as ctx:
|
||||
ctx.enter_context(in_env(prefix, version))
|
||||
|
||||
if len(lib_deps) > 0:
|
||||
_add_dependencies(prefix, lib_deps)
|
||||
if version != 'system':
|
||||
install_rust_with_toolchain(_rust_toolchain(version), envdir)
|
||||
|
||||
for args in packages_to_install:
|
||||
cmd_output_b(
|
||||
'cargo', 'install', '--bins', '--root', directory, *args,
|
||||
cwd=prefix.prefix_dir,
|
||||
)
|
||||
tmpdir = ctx.enter_context(tempfile.TemporaryDirectory())
|
||||
ctx.enter_context(envcontext((('RUSTUP_HOME', tmpdir),)))
|
||||
|
||||
if len(lib_deps) > 0:
|
||||
_add_dependencies(prefix, lib_deps)
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
with in_env(hook.prefix, hook.language_version):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
for args in packages_to_install:
|
||||
cmd_output_b(
|
||||
'cargo', 'install', '--bins', '--root', envdir, *args,
|
||||
cwd=prefix.prefix_dir,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
|
||||
ENVIRONMENT_DIR = None
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
install_environment = helpers.no_install
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:])
|
||||
return helpers.run_xargs(hook, cmd, file_args, color=color)
|
||||
|
|
@ -2,25 +2,24 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import os
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.envcontext import envcontext
|
||||
from pre_commit.envcontext import PatchesT
|
||||
from pre_commit.envcontext import Var
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output_b
|
||||
|
||||
ENVIRONMENT_DIR = 'swift_env'
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
BUILD_DIR = '.build'
|
||||
BUILD_CONFIG = 'release'
|
||||
|
||||
ENVIRONMENT_DIR = 'swift_env'
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
run_hook = lang_base.basic_run_hook
|
||||
|
||||
|
||||
def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover
|
||||
bin_path = os.path.join(venv, BUILD_DIR, BUILD_CONFIG)
|
||||
|
|
@ -28,10 +27,8 @@ def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover
|
|||
|
||||
|
||||
@contextlib.contextmanager # pragma: win32 no cover
|
||||
def in_env(prefix: Prefix) -> Generator[None, None, None]:
|
||||
envdir = prefix.path(
|
||||
helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
|
||||
)
|
||||
def in_env(prefix: Prefix, version: str) -> Generator[None]:
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
with envcontext(get_env_patch(envdir)):
|
||||
yield
|
||||
|
||||
|
|
@ -39,27 +36,15 @@ def in_env(prefix: Prefix) -> Generator[None, None, None]:
|
|||
def install_environment(
|
||||
prefix: Prefix, version: str, additional_dependencies: Sequence[str],
|
||||
) -> None: # pragma: win32 no cover
|
||||
helpers.assert_version_default('swift', version)
|
||||
helpers.assert_no_additional_deps('swift', additional_dependencies)
|
||||
directory = prefix.path(
|
||||
helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
|
||||
)
|
||||
lang_base.assert_version_default('swift', version)
|
||||
lang_base.assert_no_additional_deps('swift', additional_dependencies)
|
||||
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||
|
||||
# Build the swift package
|
||||
with clean_path_on_failure(directory):
|
||||
os.mkdir(directory)
|
||||
cmd_output_b(
|
||||
'swift', 'build',
|
||||
'-C', prefix.prefix_dir,
|
||||
'-c', BUILD_CONFIG,
|
||||
'--build-path', os.path.join(directory, BUILD_DIR),
|
||||
)
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]: # pragma: win32 no cover
|
||||
with in_env(hook.prefix):
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
os.mkdir(envdir)
|
||||
cmd_output_b(
|
||||
'swift', 'build',
|
||||
'--package-path', prefix.prefix_dir,
|
||||
'-c', BUILD_CONFIG,
|
||||
'--build-path', os.path.join(envdir, BUILD_DIR),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages import helpers
|
||||
|
||||
|
||||
ENVIRONMENT_DIR = None
|
||||
get_default_version = helpers.basic_get_default_version
|
||||
health_check = helpers.basic_health_check
|
||||
install_environment = helpers.no_install
|
||||
|
||||
|
||||
def run_hook(
|
||||
hook: Hook,
|
||||
file_args: Sequence[str],
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
|
||||
10
pre_commit/languages/unsupported.py
Normal file
10
pre_commit/languages/unsupported.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit import lang_base
|
||||
|
||||
ENVIRONMENT_DIR = None
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
install_environment = lang_base.no_install
|
||||
in_env = lang_base.no_env
|
||||
run_hook = lang_base.basic_run_hook
|
||||
32
pre_commit/languages/unsupported_script.py
Normal file
32
pre_commit/languages/unsupported_script.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import lang_base
|
||||
from pre_commit.prefix import Prefix
|
||||
|
||||
ENVIRONMENT_DIR = None
|
||||
get_default_version = lang_base.basic_get_default_version
|
||||
health_check = lang_base.basic_health_check
|
||||
install_environment = lang_base.no_install
|
||||
in_env = lang_base.no_env
|
||||
|
||||
|
||||
def run_hook(
|
||||
prefix: Prefix,
|
||||
entry: str,
|
||||
args: Sequence[str],
|
||||
file_args: Sequence[str],
|
||||
*,
|
||||
is_local: bool,
|
||||
require_serial: bool,
|
||||
color: bool,
|
||||
) -> tuple[int, bytes]:
|
||||
cmd = lang_base.hook_cmd(entry, args)
|
||||
cmd = (prefix.path(cmd[0]), *cmd[1:])
|
||||
return lang_base.run_xargs(
|
||||
cmd,
|
||||
file_args,
|
||||
require_serial=require_serial,
|
||||
color=color,
|
||||
)
|
||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import Generator
|
||||
from collections.abc import Generator
|
||||
|
||||
from pre_commit import color
|
||||
from pre_commit import output
|
||||
|
|
@ -32,7 +32,7 @@ class LoggingHandler(logging.Handler):
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def logging_handler(use_color: bool) -> Generator[None, None, None]:
|
||||
def logging_handler(use_color: bool) -> Generator[None]:
|
||||
handler = LoggingHandler(use_color)
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import argparse
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import clientlib
|
||||
from pre_commit import git
|
||||
from pre_commit.color import add_color_option
|
||||
from pre_commit.commands import hazmat
|
||||
from pre_commit.commands.autoupdate import autoupdate
|
||||
from pre_commit.commands.clean import clean
|
||||
from pre_commit.commands.gc import gc
|
||||
|
|
@ -36,8 +38,11 @@ logger = logging.getLogger('pre_commit')
|
|||
# pyvenv
|
||||
os.environ.pop('__PYVENV_LAUNCHER__', None)
|
||||
|
||||
# https://github.com/getsentry/snuba/pull/5388
|
||||
os.environ.pop('PYTHONEXECUTABLE', None)
|
||||
|
||||
COMMANDS_NO_GIT = {
|
||||
'clean', 'gc', 'init-templatedir', 'sample-config',
|
||||
'clean', 'gc', 'hazmat', 'init-templatedir', 'sample-config',
|
||||
'validate-config', 'validate-manifest',
|
||||
}
|
||||
|
||||
|
|
@ -52,16 +57,16 @@ def _add_config_option(parser: argparse.ArgumentParser) -> None:
|
|||
def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
'-t', '--hook-type',
|
||||
choices=C.HOOK_TYPES, action='append', dest='hook_types',
|
||||
choices=clientlib.HOOK_TYPES, action='append', dest='hook_types',
|
||||
)
|
||||
|
||||
|
||||
def _add_run_options(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument('hook', nargs='?', help='A single hook-id to run')
|
||||
parser.add_argument('--verbose', '-v', action='store_true', default=False)
|
||||
parser.add_argument('--verbose', '-v', action='store_true')
|
||||
mutex_group = parser.add_mutually_exclusive_group(required=False)
|
||||
mutex_group.add_argument(
|
||||
'--all-files', '-a', action='store_true', default=False,
|
||||
'--all-files', '-a', action='store_true',
|
||||
help='Run on all the files in the repo.',
|
||||
)
|
||||
mutex_group.add_argument(
|
||||
|
|
@ -73,7 +78,14 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
|
|||
help='When hooks fail, run `git diff` directly afterward.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--hook-stage', choices=C.STAGES, default='commit',
|
||||
'--fail-fast', action='store_true',
|
||||
help='Stop after the first failing hook.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--hook-stage',
|
||||
choices=clientlib.STAGES,
|
||||
type=clientlib.transform_stage,
|
||||
default='pre-commit',
|
||||
help='The stage during which the hook is fired. One of %(choices)s',
|
||||
)
|
||||
parser.add_argument(
|
||||
|
|
@ -103,6 +115,17 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
|
|||
'now checked out.'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--pre-rebase-upstream', help=(
|
||||
'The upstream from which the series was forked.'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--pre-rebase-branch', help=(
|
||||
'The branch being rebased, and is not set when '
|
||||
'rebasing the current branch.'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--commit-msg-filename',
|
||||
help='Filename to check when running during `commit-msg`',
|
||||
|
|
@ -211,14 +234,23 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
help='Store "frozen" hashes in `rev` instead of tag names',
|
||||
)
|
||||
autoupdate_parser.add_argument(
|
||||
'--repo', dest='repos', action='append', metavar='REPO',
|
||||
'--repo', dest='repos', action='append', metavar='REPO', default=[],
|
||||
help='Only update this repository -- may be specified multiple times.',
|
||||
)
|
||||
autoupdate_parser.add_argument(
|
||||
'-j', '--jobs', type=int, default=1,
|
||||
help='Number of threads to use. (default %(default)s).',
|
||||
)
|
||||
|
||||
_add_cmd('clean', help='Clean out pre-commit files.')
|
||||
|
||||
_add_cmd('gc', help='Clean unused cached repos.')
|
||||
|
||||
hazmat_parser = _add_cmd(
|
||||
'hazmat', help='Composable tools for rare use in hook `entry`.',
|
||||
)
|
||||
hazmat.add_parsers(hazmat_parser)
|
||||
|
||||
init_templatedir_parser = _add_cmd(
|
||||
'init-templatedir',
|
||||
help=(
|
||||
|
|
@ -253,7 +285,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
)
|
||||
_add_hook_type_option(install_parser)
|
||||
install_parser.add_argument(
|
||||
'--allow-missing-config', action='store_true', default=False,
|
||||
'--allow-missing-config', action='store_true',
|
||||
help=(
|
||||
'Whether to allow a missing `pre-commit` configuration file '
|
||||
'or exit with a failure code.'
|
||||
|
|
@ -353,15 +385,18 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
|
||||
if args.command == 'autoupdate':
|
||||
return autoupdate(
|
||||
args.config, store,
|
||||
args.config,
|
||||
tags_only=not args.bleeding_edge,
|
||||
freeze=args.freeze,
|
||||
repos=args.repos,
|
||||
jobs=args.jobs,
|
||||
)
|
||||
elif args.command == 'clean':
|
||||
return clean(store)
|
||||
elif args.command == 'gc':
|
||||
return gc(store)
|
||||
elif args.command == 'hazmat':
|
||||
return hazmat.impl(args)
|
||||
elif args.command == 'hook-impl':
|
||||
return hook_impl(
|
||||
store,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import git
|
||||
|
|
@ -21,7 +21,7 @@ def check_all_hooks_match_files(config_file: str) -> int:
|
|||
for hook in all_hooks(config, Store()):
|
||||
if hook.always_run or hook.language == 'fail':
|
||||
continue
|
||||
elif not classifier.filenames_for_hook(hook):
|
||||
elif not any(classifier.filenames_for_hook(hook)):
|
||||
print(f'{hook.id} does not apply to this repository')
|
||||
retv = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ from __future__ import annotations
|
|||
|
||||
import argparse
|
||||
import re
|
||||
from typing import Sequence
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Sequence
|
||||
|
||||
from cfgv import apply_defaults
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ from pre_commit.commands.run import Classifier
|
|||
|
||||
|
||||
def exclude_matches_any(
|
||||
filenames: Sequence[str],
|
||||
filenames: Iterable[str],
|
||||
include: str,
|
||||
exclude: str,
|
||||
) -> bool:
|
||||
|
|
@ -50,11 +51,12 @@ def check_useless_excludes(config_file: str) -> int:
|
|||
# Not actually a manifest dict, but this more accurately reflects
|
||||
# the defaults applied during runtime
|
||||
hook = apply_defaults(hook, MANIFEST_HOOK_DICT)
|
||||
names = classifier.filenames
|
||||
types = hook['types']
|
||||
types_or = hook['types_or']
|
||||
exclude_types = hook['exclude_types']
|
||||
names = classifier.by_types(names, types, types_or, exclude_types)
|
||||
names = classifier.by_types(
|
||||
classifier.filenames,
|
||||
hook['types'],
|
||||
hook['types_or'],
|
||||
hook['exclude_types'],
|
||||
)
|
||||
include, exclude = hook['files'], hook['exclude']
|
||||
if not exclude_matches_any(names, include, exclude):
|
||||
print(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit import output
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
from typing import Mapping
|
||||
from collections.abc import Mapping
|
||||
from typing import NoReturn
|
||||
|
||||
from identify.identify import parse_shebang_from_file
|
||||
|
|
@ -20,13 +20,13 @@ def parse_filename(filename: str) -> tuple[str, ...]:
|
|||
|
||||
|
||||
def find_executable(
|
||||
exe: str, _environ: Mapping[str, str] | None = None,
|
||||
exe: str, *, env: Mapping[str, str] | None = None,
|
||||
) -> str | None:
|
||||
exe = os.path.normpath(exe)
|
||||
if os.sep in exe:
|
||||
return exe
|
||||
|
||||
environ = _environ if _environ is not None else os.environ
|
||||
environ = env if env is not None else os.environ
|
||||
|
||||
if 'PATHEXT' in environ:
|
||||
exts = environ['PATHEXT'].split(os.pathsep)
|
||||
|
|
@ -43,12 +43,12 @@ def find_executable(
|
|||
return None
|
||||
|
||||
|
||||
def normexe(orig: str) -> str:
|
||||
def normexe(orig: str, *, env: Mapping[str, str] | None = None) -> str:
|
||||
def _error(msg: str) -> NoReturn:
|
||||
raise ExecutableNotFoundError(f'Executable `{orig}` {msg}')
|
||||
|
||||
if os.sep not in orig and (not os.altsep or os.altsep not in orig):
|
||||
exe = find_executable(orig)
|
||||
exe = find_executable(orig, env=env)
|
||||
if exe is None:
|
||||
_error('not found')
|
||||
return exe
|
||||
|
|
@ -62,7 +62,11 @@ def normexe(orig: str) -> str:
|
|||
return orig
|
||||
|
||||
|
||||
def normalize_cmd(cmd: tuple[str, ...]) -> tuple[str, ...]:
|
||||
def normalize_cmd(
|
||||
cmd: tuple[str, ...],
|
||||
*,
|
||||
env: Mapping[str, str] | None = None,
|
||||
) -> tuple[str, ...]:
|
||||
"""Fixes for the following issues on windows
|
||||
- https://bugs.python.org/issue8557
|
||||
- windows does not parse shebangs
|
||||
|
|
@ -70,12 +74,12 @@ def normalize_cmd(cmd: tuple[str, ...]) -> tuple[str, ...]:
|
|||
This function also makes deep-path shebangs work just fine
|
||||
"""
|
||||
# Use PATH to determine the executable
|
||||
exe = normexe(cmd[0])
|
||||
exe = normexe(cmd[0], env=env)
|
||||
|
||||
# Figure out the shebang from the resulting command
|
||||
cmd = parse_filename(exe) + (exe,) + cmd[1:]
|
||||
|
||||
# This could have given us back another bare executable
|
||||
exe = normexe(cmd[0])
|
||||
exe = normexe(cmd[0], env=env)
|
||||
|
||||
return (exe,) + cmd[1:]
|
||||
|
|
|
|||
|
|
@ -3,35 +3,39 @@ from __future__ import annotations
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit.all_languages import languages
|
||||
from pre_commit.clientlib import load_manifest
|
||||
from pre_commit.clientlib import LOCAL
|
||||
from pre_commit.clientlib import META
|
||||
from pre_commit.hook import Hook
|
||||
from pre_commit.languages.all import languages
|
||||
from pre_commit.languages.helpers import environment_dir
|
||||
from pre_commit.lang_base import environment_dir
|
||||
from pre_commit.prefix import Prefix
|
||||
from pre_commit.store import Store
|
||||
from pre_commit.util import parse_version
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import rmtree
|
||||
|
||||
|
||||
logger = logging.getLogger('pre_commit')
|
||||
|
||||
|
||||
def _state_filename_v1(venv: str) -> str:
|
||||
return os.path.join(venv, '.install_state_v1')
|
||||
|
||||
|
||||
def _state_filename_v2(venv: str) -> str:
|
||||
return os.path.join(venv, '.install_state_v2')
|
||||
|
||||
|
||||
def _state(additional_deps: Sequence[str]) -> object:
|
||||
return {'additional_dependencies': sorted(additional_deps)}
|
||||
return {'additional_dependencies': additional_deps}
|
||||
|
||||
|
||||
def _state_filename(prefix: Prefix, venv: str) -> str:
|
||||
return prefix.path(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}')
|
||||
|
||||
|
||||
def _read_state(prefix: Prefix, venv: str) -> object | None:
|
||||
filename = _state_filename(prefix, venv)
|
||||
def _read_state(venv: str) -> object | None:
|
||||
filename = _state_filename_v1(venv)
|
||||
if not os.path.exists(filename):
|
||||
return None
|
||||
else:
|
||||
|
|
@ -39,26 +43,22 @@ def _read_state(prefix: Prefix, venv: str) -> object | None:
|
|||
return json.load(f)
|
||||
|
||||
|
||||
def _write_state(prefix: Prefix, venv: str, state: object) -> None:
|
||||
state_filename = _state_filename(prefix, venv)
|
||||
staging = f'{state_filename}staging'
|
||||
with open(staging, 'w') as state_file:
|
||||
state_file.write(json.dumps(state))
|
||||
# Move the file into place atomically to indicate we've installed
|
||||
os.replace(staging, state_filename)
|
||||
|
||||
|
||||
def _hook_installed(hook: Hook) -> bool:
|
||||
lang = languages[hook.language]
|
||||
venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version)
|
||||
if lang.ENVIRONMENT_DIR is None:
|
||||
return True
|
||||
|
||||
venv = environment_dir(
|
||||
hook.prefix,
|
||||
lang.ENVIRONMENT_DIR,
|
||||
hook.language_version,
|
||||
)
|
||||
return (
|
||||
venv is None or (
|
||||
(
|
||||
_read_state(hook.prefix, venv) ==
|
||||
_state(hook.additional_dependencies)
|
||||
) and
|
||||
not lang.health_check(hook.prefix, hook.language_version)
|
||||
)
|
||||
(
|
||||
os.path.exists(_state_filename_v2(venv)) or
|
||||
_read_state(venv) == _state(hook.additional_dependencies)
|
||||
) and
|
||||
not lang.health_check(hook.prefix, hook.language_version)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -69,26 +69,41 @@ def _hook_install(hook: Hook) -> None:
|
|||
|
||||
lang = languages[hook.language]
|
||||
assert lang.ENVIRONMENT_DIR is not None
|
||||
venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version)
|
||||
|
||||
venv = environment_dir(
|
||||
hook.prefix,
|
||||
lang.ENVIRONMENT_DIR,
|
||||
hook.language_version,
|
||||
)
|
||||
|
||||
# There's potentially incomplete cleanup from previous runs
|
||||
# Clean it up!
|
||||
if hook.prefix.exists(venv):
|
||||
rmtree(hook.prefix.path(venv))
|
||||
if os.path.exists(venv):
|
||||
rmtree(venv)
|
||||
|
||||
lang.install_environment(
|
||||
hook.prefix, hook.language_version, hook.additional_dependencies,
|
||||
)
|
||||
health_error = lang.health_check(hook.prefix, hook.language_version)
|
||||
if health_error:
|
||||
raise AssertionError(
|
||||
f'BUG: expected environment for {hook.language} to be healthy '
|
||||
f'immediately after install, please open an issue describing '
|
||||
f'your environment\n\n'
|
||||
f'more info:\n\n{health_error}',
|
||||
with clean_path_on_failure(venv):
|
||||
lang.install_environment(
|
||||
hook.prefix, hook.language_version, hook.additional_dependencies,
|
||||
)
|
||||
# Write our state to indicate we're installed
|
||||
_write_state(hook.prefix, venv, _state(hook.additional_dependencies))
|
||||
health_error = lang.health_check(hook.prefix, hook.language_version)
|
||||
if health_error:
|
||||
raise AssertionError(
|
||||
f'BUG: expected environment for {hook.language} to be healthy '
|
||||
f'immediately after install, please open an issue describing '
|
||||
f'your environment\n\n'
|
||||
f'more info:\n\n{health_error}',
|
||||
)
|
||||
|
||||
# TODO: remove v1 state writing, no longer needed after pre-commit 3.0
|
||||
# Write our state to indicate we're installed
|
||||
state_filename = _state_filename_v1(venv)
|
||||
staging = f'{state_filename}staging'
|
||||
with open(staging, 'w') as state_file:
|
||||
state_file.write(json.dumps(_state(hook.additional_dependencies)))
|
||||
# Move the file into place atomically to indicate we've installed
|
||||
os.replace(staging, state_filename)
|
||||
|
||||
open(_state_filename_v2(venv), 'a+').close()
|
||||
|
||||
|
||||
def _hook(
|
||||
|
|
@ -99,15 +114,6 @@ def _hook(
|
|||
for dct in rest:
|
||||
ret.update(dct)
|
||||
|
||||
version = ret['minimum_pre_commit_version']
|
||||
if parse_version(version) > parse_version(C.VERSION):
|
||||
logger.error(
|
||||
f'The hook `{ret["id"]}` requires pre-commit version {version} '
|
||||
f'but version {C.VERSION} is installed. '
|
||||
f'Perhaps run `pip install --upgrade pre-commit`.',
|
||||
)
|
||||
exit(1)
|
||||
|
||||
lang = ret['language']
|
||||
if ret['language_version'] == C.DEFAULT:
|
||||
ret['language_version'] = root_config['default_language_version'][lang]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: pre_commit_empty_pubspec
|
||||
environment:
|
||||
sdk: '>=2.10.0'
|
||||
sdk: '>=2.12.0'
|
||||
executables: {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from setuptools import setup
|
||||
|
||||
|
||||
setup(name='pre-commit-placeholder-package', version='0.0.0')
|
||||
setup(name='pre-commit-placeholder-package', version='0.0.0', py_modules=[])
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -4,9 +4,10 @@ import contextlib
|
|||
import logging
|
||||
import os.path
|
||||
import time
|
||||
from typing import Generator
|
||||
from collections.abc import Generator
|
||||
|
||||
from pre_commit import git
|
||||
from pre_commit.errors import FatalError
|
||||
from pre_commit.util import CalledProcessError
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import cmd_output_b
|
||||
|
|
@ -32,7 +33,7 @@ def _git_apply(patch: str) -> None:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _intent_to_add_cleared() -> Generator[None, None, None]:
|
||||
def _intent_to_add_cleared() -> Generator[None]:
|
||||
intent_to_add = git.intent_to_add_files()
|
||||
if intent_to_add:
|
||||
logger.warning('Unstaged intent-to-add files detected.')
|
||||
|
|
@ -47,14 +48,23 @@ def _intent_to_add_cleared() -> Generator[None, None, None]:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
|
||||
def _unstaged_changes_cleared(patch_dir: str) -> Generator[None]:
|
||||
tree = cmd_output('git', 'write-tree')[1].strip()
|
||||
retcode, diff_stdout_binary, _ = cmd_output_b(
|
||||
diff_cmd = (
|
||||
'git', 'diff-index', '--ignore-submodules', '--binary',
|
||||
'--exit-code', '--no-color', '--no-ext-diff', tree, '--',
|
||||
check=False,
|
||||
)
|
||||
if retcode and diff_stdout_binary.strip():
|
||||
retcode, diff_stdout, diff_stderr = cmd_output_b(*diff_cmd, check=False)
|
||||
if retcode == 0:
|
||||
# There weren't any staged files so we don't need to do anything
|
||||
# special
|
||||
yield
|
||||
elif retcode == 1 and not diff_stdout.strip():
|
||||
# due to behaviour (probably a bug?) in git with crlf endings and
|
||||
# autocrlf set to either `true` or `input` sometimes git will refuse
|
||||
# to show a crlf-only diff to us :(
|
||||
yield
|
||||
elif retcode == 1 and diff_stdout.strip():
|
||||
patch_filename = f'patch{int(time.time())}-{os.getpid()}'
|
||||
patch_filename = os.path.join(patch_dir, patch_filename)
|
||||
logger.warning('Unstaged files detected.')
|
||||
|
|
@ -62,7 +72,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
|
|||
# Save the current unstaged changes as a patch
|
||||
os.makedirs(patch_dir, exist_ok=True)
|
||||
with open(patch_filename, 'wb') as patch_file:
|
||||
patch_file.write(diff_stdout_binary)
|
||||
patch_file.write(diff_stdout)
|
||||
|
||||
# prevent recursive post-checkout hooks (#1418)
|
||||
no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1')
|
||||
|
|
@ -86,14 +96,16 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
|
|||
_git_apply(patch_filename)
|
||||
|
||||
logger.info(f'Restored changes from {patch_filename}.')
|
||||
else:
|
||||
# There weren't any staged files so we don't need to do anything
|
||||
# special
|
||||
yield
|
||||
else: # pragma: win32 no cover
|
||||
# some error occurred while requesting the diff
|
||||
e = CalledProcessError(retcode, diff_cmd, b'', diff_stderr)
|
||||
raise FatalError(
|
||||
f'pre-commit failed to diff -- perhaps due to permissions?\n\n{e}',
|
||||
)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def staged_files_only(patch_dir: str) -> Generator[None, None, None]:
|
||||
def staged_files_only(patch_dir: str) -> Generator[None]:
|
||||
"""Clear any unstaged changes from the git working directory inside this
|
||||
context.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@ import logging
|
|||
import os.path
|
||||
import sqlite3
|
||||
import tempfile
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pre_commit.constants as C
|
||||
from pre_commit import clientlib
|
||||
from pre_commit import file_lock
|
||||
from pre_commit import git
|
||||
from pre_commit.util import CalledProcessError
|
||||
from pre_commit.util import clean_path_on_failure
|
||||
from pre_commit.util import cmd_output_b
|
||||
from pre_commit.util import resource_text
|
||||
from pre_commit.util import rmtree
|
||||
|
||||
|
||||
logger = logging.getLogger('pre_commit')
|
||||
|
|
@ -36,6 +36,26 @@ def _get_default_directory() -> str:
|
|||
return os.path.realpath(ret)
|
||||
|
||||
|
||||
_LOCAL_RESOURCES = (
|
||||
'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore',
|
||||
'package.json', 'pre-commit-package-dev-1.rockspec',
|
||||
'pre_commit_placeholder_package.gemspec', 'setup.py',
|
||||
'environment.yml', 'Makefile.PL', 'pubspec.yaml',
|
||||
'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv',
|
||||
)
|
||||
|
||||
|
||||
def _make_local_repo(directory: str) -> None:
|
||||
for resource in _LOCAL_RESOURCES:
|
||||
resource_dirname, resource_basename = os.path.split(resource)
|
||||
contents = resource_text(f'empty_template_{resource_basename}')
|
||||
target_dir = os.path.join(directory, resource_dirname)
|
||||
target_file = os.path.join(target_dir, resource_basename)
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
with open(target_file, 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
|
||||
class Store:
|
||||
get_default_directory = staticmethod(_get_default_directory)
|
||||
|
||||
|
|
@ -75,13 +95,13 @@ class Store:
|
|||
' PRIMARY KEY (repo, ref)'
|
||||
');',
|
||||
)
|
||||
self._create_config_table(db)
|
||||
self._create_configs_table(db)
|
||||
|
||||
# Atomic file move
|
||||
os.replace(tmpfile, self.db_path)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def exclusive_lock(self) -> Generator[None, None, None]:
|
||||
def exclusive_lock(self) -> Generator[None]:
|
||||
def blocked_cb() -> None: # pragma: no cover (tests are in-process)
|
||||
logger.info('Locking pre-commit directory')
|
||||
|
||||
|
|
@ -92,7 +112,7 @@ class Store:
|
|||
def connect(
|
||||
self,
|
||||
db_path: str | None = None,
|
||||
) -> Generator[sqlite3.Connection, None, None]:
|
||||
) -> Generator[sqlite3.Connection]:
|
||||
db_path = db_path or self.db_path
|
||||
# sqlite doesn't close its fd with its contextmanager >.<
|
||||
# contextlib.closing fixes this.
|
||||
|
|
@ -105,7 +125,7 @@ class Store:
|
|||
@classmethod
|
||||
def db_repo_name(cls, repo: str, deps: Sequence[str]) -> str:
|
||||
if deps:
|
||||
return f'{repo}:{",".join(sorted(deps))}'
|
||||
return f'{repo}:{",".join(deps)}'
|
||||
else:
|
||||
return repo
|
||||
|
||||
|
|
@ -116,6 +136,7 @@ class Store:
|
|||
deps: Sequence[str],
|
||||
make_strategy: Callable[[str], None],
|
||||
) -> str:
|
||||
original_repo = repo
|
||||
repo = self.db_repo_name(repo, deps)
|
||||
|
||||
def _get_result() -> str | None:
|
||||
|
|
@ -148,6 +169,9 @@ class Store:
|
|||
'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)',
|
||||
[repo, ref, directory],
|
||||
)
|
||||
|
||||
clientlib.warn_for_stages_on_repo_init(original_repo, directory)
|
||||
|
||||
return directory
|
||||
|
||||
def _complete_clone(self, ref: str, git_cmd: Callable[..., None]) -> None:
|
||||
|
|
@ -185,40 +209,12 @@ class Store:
|
|||
|
||||
return self._new_repo(repo, ref, deps, clone_strategy)
|
||||
|
||||
LOCAL_RESOURCES = (
|
||||
'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore',
|
||||
'package.json', 'pre-commit-package-dev-1.rockspec',
|
||||
'pre_commit_placeholder_package.gemspec', 'setup.py',
|
||||
'environment.yml', 'Makefile.PL', 'pubspec.yaml',
|
||||
'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv',
|
||||
)
|
||||
|
||||
def make_local(self, deps: Sequence[str]) -> str:
|
||||
def make_local_strategy(directory: str) -> None:
|
||||
for resource in self.LOCAL_RESOURCES:
|
||||
resource_dirname, resource_basename = os.path.split(resource)
|
||||
contents = resource_text(f'empty_template_{resource_basename}')
|
||||
target_dir = os.path.join(directory, resource_dirname)
|
||||
target_file = os.path.join(target_dir, resource_basename)
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
with open(target_file, 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
env = git.no_git_env()
|
||||
|
||||
# initialize the git repository so it looks more like cloned repos
|
||||
def _git_cmd(*args: str) -> None:
|
||||
cmd_output_b('git', *args, cwd=directory, env=env)
|
||||
|
||||
git.init_repo(directory, '<<unknown>>')
|
||||
_git_cmd('add', '.')
|
||||
git.commit(repo=directory)
|
||||
|
||||
return self._new_repo(
|
||||
'local', C.LOCAL_REPO_VERSION, deps, make_local_strategy,
|
||||
'local', C.LOCAL_REPO_VERSION, deps, _make_local_repo,
|
||||
)
|
||||
|
||||
def _create_config_table(self, db: sqlite3.Connection) -> None:
|
||||
def _create_configs_table(self, db: sqlite3.Connection) -> None:
|
||||
db.executescript(
|
||||
'CREATE TABLE IF NOT EXISTS configs ('
|
||||
' path TEXT NOT NULL,'
|
||||
|
|
@ -235,28 +231,5 @@ class Store:
|
|||
return
|
||||
with self.connect() as db:
|
||||
# TODO: eventually remove this and only create in _create
|
||||
self._create_config_table(db)
|
||||
self._create_configs_table(db)
|
||||
db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,))
|
||||
|
||||
def select_all_configs(self) -> list[str]:
|
||||
with self.connect() as db:
|
||||
self._create_config_table(db)
|
||||
rows = db.execute('SELECT path FROM configs').fetchall()
|
||||
return [path for path, in rows]
|
||||
|
||||
def delete_configs(self, configs: list[str]) -> None:
|
||||
with self.connect() as db:
|
||||
rows = [(path,) for path in configs]
|
||||
db.executemany('DELETE FROM configs WHERE path = ?', rows)
|
||||
|
||||
def select_all_repos(self) -> list[tuple[str, str, str]]:
|
||||
with self.connect() as db:
|
||||
return db.execute('SELECT repo, ref, path from repos').fetchall()
|
||||
|
||||
def delete_repo(self, db_repo_name: str, ref: str, path: str) -> None:
|
||||
with self.connect() as db:
|
||||
db.execute(
|
||||
'DELETE FROM repos WHERE repo = ? and ref = ?',
|
||||
(db_repo_name, ref),
|
||||
)
|
||||
rmtree(path)
|
||||
|
|
|
|||
|
|
@ -2,36 +2,19 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import errno
|
||||
import functools
|
||||
import importlib.resources
|
||||
import os.path
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Generator
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import IO
|
||||
|
||||
import yaml
|
||||
|
||||
from pre_commit import parse_shebang
|
||||
|
||||
Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
|
||||
yaml_load = functools.partial(yaml.load, Loader=Loader)
|
||||
Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper)
|
||||
|
||||
|
||||
def yaml_dump(o: Any, **kwargs: Any) -> str:
|
||||
# when python/mypy#1484 is solved, this can be `functools.partial`
|
||||
return yaml.dump(
|
||||
o, Dumper=Dumper, default_flow_style=False, indent=4, sort_keys=False,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def force_bytes(exc: Any) -> bytes:
|
||||
with contextlib.suppress(TypeError):
|
||||
|
|
@ -42,7 +25,7 @@ def force_bytes(exc: Any) -> bytes:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def clean_path_on_failure(path: str) -> Generator[None, None, None]:
|
||||
def clean_path_on_failure(path: str) -> Generator[None]:
|
||||
"""Cleans up the directory on an exceptional failure."""
|
||||
try:
|
||||
yield
|
||||
|
|
@ -52,24 +35,9 @@ def clean_path_on_failure(path: str) -> Generator[None, None, None]:
|
|||
raise
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def tmpdir() -> Generator[str, None, None]:
|
||||
"""Contextmanager to create a temporary directory. It will be cleaned up
|
||||
afterwards.
|
||||
"""
|
||||
tempdir = tempfile.mkdtemp()
|
||||
try:
|
||||
yield tempdir
|
||||
finally:
|
||||
rmtree(tempdir)
|
||||
|
||||
|
||||
def resource_bytesio(filename: str) -> IO[bytes]:
|
||||
return importlib.resources.open_binary('pre_commit.resources', filename)
|
||||
|
||||
|
||||
def resource_text(filename: str) -> str:
|
||||
return importlib.resources.read_text('pre_commit.resources', filename)
|
||||
files = importlib.resources.files('pre_commit.resources')
|
||||
return files.joinpath(filename).read_text()
|
||||
|
||||
|
||||
def make_executable(filename: str) -> None:
|
||||
|
|
@ -95,7 +63,7 @@ class CalledProcessError(RuntimeError):
|
|||
def __bytes__(self) -> bytes:
|
||||
def _indent_or_none(part: bytes | None) -> bytes:
|
||||
if part:
|
||||
return b'\n ' + part.replace(b'\n', b'\n ')
|
||||
return b'\n ' + part.replace(b'\n', b'\n ').rstrip()
|
||||
else:
|
||||
return b' (none)'
|
||||
|
||||
|
|
@ -127,7 +95,7 @@ def cmd_output_b(
|
|||
_setdefault_kwargs(kwargs)
|
||||
|
||||
try:
|
||||
cmd = parse_shebang.normalize_cmd(cmd)
|
||||
cmd = parse_shebang.normalize_cmd(cmd, env=kwargs.get('env'))
|
||||
except parse_shebang.ExecutableNotFoundError as e:
|
||||
returncode, stdout_b, stderr_b = e.to_output()
|
||||
else:
|
||||
|
|
@ -152,7 +120,7 @@ def cmd_output(*cmd: str, **kwargs: Any) -> tuple[int, str, str | None]:
|
|||
return returncode, stdout, stderr
|
||||
|
||||
|
||||
if os.name != 'nt': # pragma: win32 no cover
|
||||
if sys.platform != 'win32': # pragma: win32 no cover
|
||||
from os import openpty
|
||||
import termios
|
||||
|
||||
|
|
@ -234,29 +202,37 @@ else: # pragma: no cover
|
|||
cmd_output_p = cmd_output_b
|
||||
|
||||
|
||||
def rmtree(path: str) -> None:
|
||||
"""On windows, rmtree fails for readonly dirs."""
|
||||
def handle_remove_readonly(
|
||||
func: Callable[..., Any],
|
||||
path: str,
|
||||
exc: tuple[type[OSError], OSError, TracebackType],
|
||||
def _handle_readonly(
|
||||
func: Callable[[str], object],
|
||||
path: str,
|
||||
exc: BaseException,
|
||||
) -> None:
|
||||
if (
|
||||
func in (os.rmdir, os.remove, os.unlink) and
|
||||
isinstance(exc, OSError) and
|
||||
exc.errno in {errno.EACCES, errno.EPERM}
|
||||
):
|
||||
for p in (path, os.path.dirname(path)):
|
||||
os.chmod(p, os.stat(p).st_mode | stat.S_IWUSR)
|
||||
func(path)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
if sys.version_info < (3, 12): # pragma: <3.12 cover
|
||||
def _handle_readonly_old(
|
||||
func: Callable[[str], object],
|
||||
path: str,
|
||||
excinfo: tuple[type[BaseException], BaseException, TracebackType],
|
||||
) -> None:
|
||||
excvalue = exc[1]
|
||||
if (
|
||||
func in (os.rmdir, os.remove, os.unlink) and
|
||||
excvalue.errno in {errno.EACCES, errno.EPERM}
|
||||
):
|
||||
for p in (path, os.path.dirname(path)):
|
||||
os.chmod(p, os.stat(p).st_mode | stat.S_IWUSR)
|
||||
func(path)
|
||||
else:
|
||||
raise
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly)
|
||||
return _handle_readonly(func, path, excinfo[1])
|
||||
|
||||
|
||||
def parse_version(s: str) -> tuple[int, ...]:
|
||||
"""poor man's version comparison"""
|
||||
return tuple(int(p) for p in s.split('.'))
|
||||
def rmtree(path: str) -> None:
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=_handle_readonly_old)
|
||||
else: # pragma: >=3.12 cover
|
||||
def rmtree(path: str) -> None:
|
||||
"""On windows, rmtree fails for readonly dirs."""
|
||||
shutil.rmtree(path, ignore_errors=False, onexc=_handle_readonly)
|
||||
|
||||
|
||||
def win_exe(s: str) -> str:
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ from __future__ import annotations
|
|||
import concurrent.futures
|
||||
import contextlib
|
||||
import math
|
||||
import multiprocessing
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import MutableMapping
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import MutableMapping
|
||||
from typing import Sequence
|
||||
from typing import TypeVar
|
||||
|
||||
from pre_commit import parse_shebang
|
||||
|
|
@ -22,6 +23,21 @@ TArg = TypeVar('TArg')
|
|||
TRet = TypeVar('TRet')
|
||||
|
||||
|
||||
def cpu_count() -> int:
|
||||
try:
|
||||
# On systems that support it, this will return a more accurate count of
|
||||
# usable CPUs for the current process, which will take into account
|
||||
# cgroup limits
|
||||
return len(os.sched_getaffinity(0))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return multiprocessing.cpu_count()
|
||||
except NotImplementedError:
|
||||
return 1
|
||||
|
||||
|
||||
def _environ_size(_env: MutableMapping[str, str] | None = None) -> int:
|
||||
environ = _env if _env is not None else getattr(os, 'environb', os.environ)
|
||||
size = 8 * len(environ) # number of pointers in `envp`
|
||||
|
|
@ -104,7 +120,6 @@ def partition(
|
|||
@contextlib.contextmanager
|
||||
def _thread_mapper(maxsize: int) -> Generator[
|
||||
Callable[[Callable[[TArg], TRet], Iterable[TArg]], Iterable[TRet]],
|
||||
None, None,
|
||||
]:
|
||||
if maxsize == 1:
|
||||
yield map
|
||||
|
|
@ -162,7 +177,8 @@ def xargs(
|
|||
results = thread_map(run_cmd_partition, partitions)
|
||||
|
||||
for proc_retcode, proc_out, _ in results:
|
||||
retcode = max(retcode, proc_retcode)
|
||||
if abs(proc_retcode) > abs(retcode):
|
||||
retcode = proc_retcode
|
||||
stdout += proc_out
|
||||
|
||||
return retcode, stdout
|
||||
|
|
|
|||
19
pre_commit/yaml.py
Normal file
19
pre_commit/yaml.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
|
||||
yaml_compose = functools.partial(yaml.compose, Loader=Loader)
|
||||
yaml_load = functools.partial(yaml.load, Loader=Loader)
|
||||
Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper)
|
||||
|
||||
|
||||
def yaml_dump(o: Any, **kwargs: Any) -> str:
|
||||
# when python/mypy#1484 is solved, this can be `functools.partial`
|
||||
return yaml.dump(
|
||||
o, Dumper=Dumper, default_flow_style=False, indent=4, sort_keys=False,
|
||||
**kwargs,
|
||||
)
|
||||
52
pre_commit/yaml_rewrite.py
Normal file
52
pre_commit/yaml_rewrite.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Iterable
|
||||
from typing import NamedTuple
|
||||
from typing import Protocol
|
||||
|
||||
from yaml.nodes import MappingNode
|
||||
from yaml.nodes import Node
|
||||
from yaml.nodes import ScalarNode
|
||||
from yaml.nodes import SequenceNode
|
||||
|
||||
|
||||
class _Matcher(Protocol):
|
||||
def match(self, n: Node) -> Generator[Node]: ...
|
||||
|
||||
|
||||
class MappingKey(NamedTuple):
|
||||
k: str
|
||||
|
||||
def match(self, n: Node) -> Generator[Node]:
|
||||
if isinstance(n, MappingNode):
|
||||
for k, _ in n.value:
|
||||
if k.value == self.k:
|
||||
yield k
|
||||
|
||||
|
||||
class MappingValue(NamedTuple):
|
||||
k: str
|
||||
|
||||
def match(self, n: Node) -> Generator[Node]:
|
||||
if isinstance(n, MappingNode):
|
||||
for k, v in n.value:
|
||||
if k.value == self.k:
|
||||
yield v
|
||||
|
||||
|
||||
class SequenceItem(NamedTuple):
|
||||
def match(self, n: Node) -> Generator[Node]:
|
||||
if isinstance(n, SequenceNode):
|
||||
yield from n.value
|
||||
|
||||
|
||||
def _match(gen: Iterable[Node], m: _Matcher) -> Iterable[Node]:
|
||||
return (n for src in gen for n in m.match(src))
|
||||
|
||||
|
||||
def match(n: Node, matcher: tuple[_Matcher, ...]) -> Generator[ScalarNode]:
|
||||
gen: Iterable[Node] = (n,)
|
||||
for m in matcher:
|
||||
gen = _match(gen, m)
|
||||
return (n for n in gen if isinstance(n, ScalarNode))
|
||||
11
setup.cfg
11
setup.cfg
|
|
@ -1,6 +1,6 @@
|
|||
[metadata]
|
||||
name = pre_commit
|
||||
version = 2.21.0
|
||||
version = 4.5.1
|
||||
description = A framework for managing and maintaining multi-language pre-commit hooks.
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
|
|
@ -8,9 +8,8 @@ url = https://github.com/pre-commit/pre-commit
|
|||
author = Anthony Sottile
|
||||
author_email = asottile@umich.edu
|
||||
license = MIT
|
||||
license_file = LICENSE
|
||||
license_files = LICENSE
|
||||
classifiers =
|
||||
License :: OSI Approved :: MIT License
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
|
|
@ -24,8 +23,7 @@ install_requires =
|
|||
nodeenv>=0.11.1
|
||||
pyyaml>=5.1
|
||||
virtualenv>=20.10.0
|
||||
importlib-metadata;python_version<"3.8"
|
||||
python_requires = >=3.7
|
||||
python_requires = >=3.10
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
|
|
@ -35,8 +33,6 @@ exclude =
|
|||
[options.entry_points]
|
||||
console_scripts =
|
||||
pre-commit = pre_commit.main:main
|
||||
pre-commit-validate-config = pre_commit.clientlib:validate_config_main
|
||||
pre-commit-validate-manifest = pre_commit.clientlib:validate_manifest_main
|
||||
|
||||
[options.package_data]
|
||||
pre_commit.resources =
|
||||
|
|
@ -56,6 +52,7 @@ check_untyped_defs = true
|
|||
disallow_any_generics = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_untyped_defs = true
|
||||
enable_error_code = deprecated
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ from pre_commit import git
|
|||
from pre_commit.clientlib import CONFIG_SCHEMA
|
||||
from pre_commit.clientlib import load_manifest
|
||||
from pre_commit.util import cmd_output
|
||||
from pre_commit.util import yaml_dump
|
||||
from pre_commit.util import yaml_load
|
||||
from pre_commit.yaml import yaml_dump
|
||||
from pre_commit.yaml import yaml_load
|
||||
from testing.util import get_resource_path
|
||||
from testing.util import git_commit
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
LANGUAGES = (
|
||||
'conda', 'coursier', 'dart', 'docker', 'docker_image', 'dotnet', 'fail',
|
||||
'golang', 'lua', 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust',
|
||||
'script', 'swift', 'system',
|
||||
)
|
||||
FIELDS = (
|
||||
'ENVIRONMENT_DIR', 'get_default_version', 'health_check',
|
||||
'install_environment', 'run_hook',
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print(f' # BEGIN GENERATED ({sys.argv[0]})')
|
||||
for lang in LANGUAGES:
|
||||
parts = [f' {lang!r}: Language(name={lang!r}']
|
||||
for k in FIELDS:
|
||||
parts.append(f', {k}={lang}.{k}')
|
||||
parts.append('), # noqa: E501')
|
||||
print(''.join(parts))
|
||||
print(' # END GENERATED')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
$wc = New-Object System.Net.WebClient
|
||||
|
||||
$coursier_url = "https://github.com/coursier/coursier/releases/download/v2.0.5/cs-x86_64-pc-win32.exe"
|
||||
$coursier_dest = "C:\coursier\cs.exe"
|
||||
$coursier_hash ="d63d497f7805261e1cd657b8aaa626f6b8f7264cdb68219b2e6be9dd882033a9"
|
||||
|
||||
New-Item -Path "C:\" -Name "coursier" -ItemType "directory"
|
||||
$wc.DownloadFile($coursier_url, $coursier_dest)
|
||||
if ((Get-FileHash $coursier_dest -Algorithm SHA256).Hash -ne $coursier_hash) {
|
||||
throw "Invalid coursier file"
|
||||
}
|
||||
|
|
@ -1,15 +1,29 @@
|
|||
#!/usr/bin/env bash
|
||||
# This is a script used in CI to install coursier
|
||||
set -euo pipefail
|
||||
|
||||
COURSIER_URL="https://github.com/coursier/coursier/releases/download/v2.0.0/cs-x86_64-pc-linux"
|
||||
COURSIER_HASH="e2e838b75bc71b16bcb77ce951ad65660c89bda7957c79a0628ec7146d35122f"
|
||||
ARTIFACT="/tmp/coursier/cs"
|
||||
if [ "$OSTYPE" = msys ]; then
|
||||
URL='https://github.com/coursier/coursier/releases/download/v2.1.0-RC4/cs-x86_64-pc-win32.zip'
|
||||
SHA256='0d07386ff0f337e3e6264f7dde29d137dda6eaa2385f29741435e0b93ccdb49d'
|
||||
TARGET='/tmp/coursier/cs.zip'
|
||||
|
||||
unpack() {
|
||||
unzip "$TARGET" -d /tmp/coursier
|
||||
mv /tmp/coursier/cs-*.exe /tmp/coursier/cs.exe
|
||||
cygpath -w /tmp/coursier >> "$GITHUB_PATH"
|
||||
}
|
||||
else
|
||||
URL='https://github.com/coursier/coursier/releases/download/v2.1.0-RC4/cs-x86_64-pc-linux.gz'
|
||||
SHA256='176e92e08ab292531aa0c4993dbc9f2c99dec79578752f3b9285f54f306db572'
|
||||
TARGET=/tmp/coursier/cs.gz
|
||||
|
||||
unpack() {
|
||||
gunzip "$TARGET"
|
||||
chmod +x /tmp/coursier/cs
|
||||
echo /tmp/coursier >> "$GITHUB_PATH"
|
||||
}
|
||||
fi
|
||||
|
||||
mkdir -p /tmp/coursier
|
||||
rm -f "$ARTIFACT"
|
||||
curl --location --silent --output "$ARTIFACT" "$COURSIER_URL"
|
||||
echo "$COURSIER_HASH $ARTIFACT" | sha256sum --check
|
||||
chmod ugo+x /tmp/coursier/cs
|
||||
|
||||
echo '##vso[task.prependpath]/tmp/coursier'
|
||||
curl --location --silent --output "$TARGET" "$URL"
|
||||
echo "$SHA256 $TARGET" | sha256sum --check
|
||||
unpack
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
VERSION=2.13.4
|
||||
VERSION=2.19.6
|
||||
|
||||
if [ "$OSTYPE" = msys ]; then
|
||||
URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-windows-x64-release.zip"
|
||||
echo "##vso[task.prependpath]$(cygpath -w /tmp/dart-sdk/bin)"
|
||||
cygpath -w /tmp/dart-sdk/bin >> "$GITHUB_PATH"
|
||||
else
|
||||
URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-linux-x64-release.zip"
|
||||
echo '##vso[task.prependpath]/tmp/dart-sdk/bin'
|
||||
echo '/tmp/dart-sdk/bin' >> "$GITHUB_PATH"
|
||||
fi
|
||||
|
||||
curl --silent --location --output /tmp/dart.zip "$URL"
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Install the runtime and package manager.
|
||||
sudo apt install lua5.3 liblua5.3-dev luarocks
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
$dir = $Env:Temp
|
||||
$urlR = "https://cran.r-project.org/bin/windows/base/old/4.0.4/R-4.0.4-win.exe"
|
||||
$outputR = "$dir\R-win.exe"
|
||||
$wcR = New-Object System.Net.WebClient
|
||||
$wcR.DownloadFile($urlR, $outputR)
|
||||
Start-Process -FilePath $outputR -ArgumentList "/S /v/qn"
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
sudo apt install r-base
|
||||
# create empty folder for user library.
|
||||
# necessary for non-root users who have
|
||||
# never installed an R package before.
|
||||
# Alternatively, we require the renv
|
||||
# package to be installed already, then we can
|
||||
# omit that.
|
||||
Rscript -e 'dir.create(Sys.getenv("R_LIBS_USER"), recursive = TRUE)'
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# This is a script used in CI to install swift
|
||||
set -euo pipefail
|
||||
|
||||
. /etc/lsb-release
|
||||
if [ "$DISTRIB_CODENAME" = "jammy" ]; then
|
||||
SWIFT_URL='https://download.swift.org/swift-5.7.1-release/ubuntu2204/swift-5.7.1-RELEASE/swift-5.7.1-RELEASE-ubuntu22.04.tar.gz'
|
||||
SWIFT_HASH='7f60291f5088d3e77b0c2364beaabd29616ee7b37260b7b06bdbeb891a7fe161'
|
||||
else
|
||||
echo "unknown dist: ${DISTRIB_CODENAME}" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check() {
|
||||
echo "$SWIFT_HASH $TGZ" | sha256sum --check
|
||||
}
|
||||
|
||||
TGZ="$HOME/.swift/swift.tar.gz"
|
||||
mkdir -p "$(dirname "$TGZ")"
|
||||
if ! check >& /dev/null; then
|
||||
rm -f "$TGZ"
|
||||
curl --location --silent --output "$TGZ" "$SWIFT_URL"
|
||||
check
|
||||
fi
|
||||
|
||||
mkdir -p /tmp/swift
|
||||
tar -xf "$TGZ" --strip 1 --directory /tmp/swift
|
||||
|
||||
echo '##vso[task.prependpath]/tmp/swift/usr/bin'
|
||||
40
testing/language_helpers.py
Normal file
40
testing/language_helpers.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
|
||||
from pre_commit.lang_base import Language
|
||||
from pre_commit.prefix import Prefix
|
||||
|
||||
|
||||
def run_language(
|
||||
path: os.PathLike[str],
|
||||
language: Language,
|
||||
exe: str,
|
||||
args: Sequence[str] = (),
|
||||
file_args: Sequence[str] = (),
|
||||
version: str | None = None,
|
||||
deps: Sequence[str] = (),
|
||||
is_local: bool = False,
|
||||
require_serial: bool = True,
|
||||
color: bool = False,
|
||||
) -> tuple[int, bytes]:
|
||||
prefix = Prefix(str(path))
|
||||
version = version or language.get_default_version()
|
||||
|
||||
if language.ENVIRONMENT_DIR is not None:
|
||||
language.install_environment(prefix, version, deps)
|
||||
health_error = language.health_check(prefix, version)
|
||||
assert health_error is None, health_error
|
||||
with language.in_env(prefix, version):
|
||||
ret, out = language.run_hook(
|
||||
prefix,
|
||||
exe,
|
||||
args,
|
||||
file_args,
|
||||
is_local=is_local,
|
||||
require_serial=require_serial,
|
||||
color=color,
|
||||
)
|
||||
out = out.replace(b'\r\n', b'\n')
|
||||
return ret, out
|
||||
92
testing/languages
Executable file
92
testing/languages
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import json
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
EXCLUDED = frozenset((
|
||||
('windows-latest', 'docker'),
|
||||
('windows-latest', 'docker_image'),
|
||||
('windows-latest', 'lua'),
|
||||
('windows-latest', 'swift'),
|
||||
))
|
||||
|
||||
|
||||
def _always_run() -> frozenset[str]:
|
||||
ret = ['.github/workflows/languages.yaml', 'testing/languages']
|
||||
ret.extend(
|
||||
os.path.join('pre_commit/resources', fname)
|
||||
for fname in os.listdir('pre_commit/resources')
|
||||
)
|
||||
return frozenset(ret)
|
||||
|
||||
|
||||
def _lang_files(lang: str) -> frozenset[str]:
|
||||
prog = f'''\
|
||||
import json
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import pre_commit.languages.{lang}
|
||||
import tests.languages.{lang}_test
|
||||
|
||||
modules = sorted(
|
||||
os.path.relpath(v.__file__)
|
||||
for k, v in sys.modules.items()
|
||||
if k.startswith(('pre_commit.', 'tests.', 'testing.'))
|
||||
)
|
||||
print(json.dumps(modules))
|
||||
'''
|
||||
out = json.loads(subprocess.check_output((sys.executable, '-c', prog)))
|
||||
return frozenset(out)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--all', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
langs = [
|
||||
os.path.splitext(fname)[0]
|
||||
for fname in sorted(os.listdir('pre_commit/languages'))
|
||||
if fname.endswith('.py') and fname != '__init__.py'
|
||||
]
|
||||
|
||||
triggers_all = _always_run()
|
||||
for fname in triggers_all:
|
||||
assert os.path.exists(fname), fname
|
||||
|
||||
if not args.all:
|
||||
with concurrent.futures.ThreadPoolExecutor(os.cpu_count()) as exe:
|
||||
by_lang = {
|
||||
lang: files | triggers_all
|
||||
for lang, files in zip(langs, exe.map(_lang_files, langs))
|
||||
}
|
||||
|
||||
diff_cmd = ('git', 'diff', '--name-only', 'origin/main...HEAD')
|
||||
files = set(subprocess.check_output(diff_cmd).decode().splitlines())
|
||||
|
||||
langs = [
|
||||
lang
|
||||
for lang, lang_files in by_lang.items()
|
||||
if lang_files & files
|
||||
]
|
||||
|
||||
matched = [
|
||||
{'os': os, 'language': lang}
|
||||
for os in ('windows-latest', 'ubuntu-latest')
|
||||
for lang in langs
|
||||
if (os, lang) not in EXCLUDED
|
||||
]
|
||||
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
|
||||
f.write(f'languages={json.dumps(matched)}\n')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -8,7 +8,7 @@ import shutil
|
|||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
# This is a script for generating the tarred resources for git repo
|
||||
|
|
@ -16,8 +16,8 @@ from typing import Sequence
|
|||
|
||||
|
||||
REPOS = (
|
||||
('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'),
|
||||
('ruby-build', 'https://github.com/rbenv/ruby-build', '98c0337'),
|
||||
('rbenv', 'https://github.com/rbenv/rbenv', '10e96bfc'),
|
||||
('ruby-build', 'https://github.com/rbenv/ruby-build', '447468b1'),
|
||||
(
|
||||
'ruby-download',
|
||||
'https://github.com/garnieretienne/rvm-download',
|
||||
|
|
@ -57,8 +57,7 @@ def make_archive(name: str, repo: str, ref: str, destdir: str) -> str:
|
|||
arcs.sort()
|
||||
|
||||
with gzip.GzipFile(output_path, 'wb', mtime=0) as gzipf:
|
||||
# https://github.com/python/typeshed/issues/5491
|
||||
with tarfile.open(fileobj=gzipf, mode='w') as tf: # type: ignore
|
||||
with tarfile.open(fileobj=gzipf, mode='w') as tf:
|
||||
for arcname, abspath in arcs:
|
||||
tf.add(
|
||||
abspath,
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
- id: sys-exec
|
||||
name: sys-exec
|
||||
entry: python -c 'import os; import sys; print(sys.executable.split(os.path.sep)[-2]) if os.name == "nt" else print(sys.executable.split(os.path.sep)[-3])'
|
||||
language: conda
|
||||
files: \.py$
|
||||
- id: additional-deps
|
||||
name: additional-deps
|
||||
entry: python
|
||||
language: conda
|
||||
files: \.py$
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
dependencies:
|
||||
- python
|
||||
- pip
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"repositories": [
|
||||
"central"
|
||||
],
|
||||
"dependencies": [
|
||||
"io.get-coursier:echo:latest.stable"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
- id: echo-java
|
||||
name: echo-java
|
||||
description: echo from java
|
||||
entry: echo-java
|
||||
language: coursier
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
- id: hello-world-dart
|
||||
name: hello world dart
|
||||
entry: hello-world-dart
|
||||
language: dart
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import 'package:ansicolor/ansicolor.dart';
|
||||
|
||||
void main() {
|
||||
AnsiPen pen = new AnsiPen()..red();
|
||||
print("hello hello " + pen("world"));
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
environment:
|
||||
sdk: '>=2.10.0 <3.0.0'
|
||||
|
||||
name: hello_world_dart
|
||||
|
||||
executables:
|
||||
hello-world-dart:
|
||||
|
||||
dependencies:
|
||||
ansicolor: ^2.0.1
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
- id: docker-hook
|
||||
name: Docker test hook
|
||||
entry: echo
|
||||
language: docker
|
||||
files: \.txt$
|
||||
|
||||
- id: docker-hook-arg
|
||||
name: Docker test hook
|
||||
entry: echo -n
|
||||
language: docker
|
||||
files: \.txt$
|
||||
|
||||
- id: docker-hook-failing
|
||||
name: Docker test hook with nonzero exit code
|
||||
entry: bork
|
||||
language: docker
|
||||
files: \.txt$
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
FROM ubuntu:focal
|
||||
|
||||
CMD ["echo", "This is overwritten by the .pre-commit-hooks.yaml 'entry'"]
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
- id: echo-entrypoint
|
||||
name: echo (via --entrypoint)
|
||||
language: docker_image
|
||||
entry: --entrypoint echo ubuntu:focal
|
||||
- id: echo-cmd
|
||||
name: echo (via cmd)
|
||||
language: docker_image
|
||||
entry: ubuntu:focal echo
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
- id: dotnet-example-hook
|
||||
name: Test Project 1
|
||||
description: Test Project 1
|
||||
entry: proj1
|
||||
language: dotnet
|
||||
stages: [commit]
|
||||
- id: proj2
|
||||
name: Test Project 2
|
||||
description: Test Project 2
|
||||
entry: proj2
|
||||
language: dotnet
|
||||
stages: [commit]
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj1", "proj1\proj1.csproj", "{38A939C3-DEA4-47D7-9B75-0418C4249662}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj2", "proj2\proj2.csproj", "{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace proj1
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.Write("Hello from dotnet!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6</TargetFramework>
|
||||
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>proj1</ToolCommandName>
|
||||
<PackageOutputPath>./nupkg</PackageOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue