git-format-staged 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -15
- package/git-format-staged +69 -10
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -128,30 +128,55 @@ option to tell it to read content from `stdin` instead of reading files from
|
|
|
128
128
|
disk, and messages from `eslint` are redirected to `stderr` (using the `>&2`
|
|
129
129
|
notation) so that you can see them.
|
|
130
130
|
|
|
131
|
-
### Set up a pre-commit hook
|
|
131
|
+
### Set up a pre-commit hook
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
a
|
|
133
|
+
A git pre-commit hook runs automatically whenever you make a commit, before your
|
|
134
|
+
editor opens to write a commit message. If formatting fails with a non-zero exit
|
|
135
|
+
status it will cancel the commit, requiring you to fix the problem before trying
|
|
136
|
+
again. (You can always skip a pre-commit hook by running `git commit
|
|
137
|
+
--no-verify`.) But in most cases you will have correctly formatted files at all
|
|
138
|
+
times, without having to think about it again after setup. Whenever a file is
|
|
139
|
+
changed as a result of formatting on commit you will see a message in the output
|
|
140
|
+
from `git commit`.
|
|
135
141
|
|
|
136
|
-
|
|
142
|
+
There are a number of options for setting up a git pre-commit hook depending on
|
|
143
|
+
the software ecosystem you are working with.
|
|
137
144
|
|
|
138
|
-
|
|
145
|
+
#### Write a pre-commit hook manually
|
|
139
146
|
|
|
140
|
-
|
|
147
|
+
You can write a hook manually by creating the file `.git/hooks/pre-commit`, and
|
|
148
|
+
making it executable. A hook set up this way will _not_ automatically propagate
|
|
149
|
+
to other clones, so every collaborator will have to do this themselves.
|
|
141
150
|
|
|
142
|
-
|
|
143
|
-
$ npm run prepare
|
|
151
|
+
#### Nix devShell
|
|
144
152
|
|
|
145
|
-
|
|
153
|
+
If you use a Nix devShell you can use [nix-git-hooks][] to help set up
|
|
154
|
+
a pre-commit hook automatically. You can either automatically install hooks for
|
|
155
|
+
everyone who uses the devShell, or provide a command that collaborators can run
|
|
156
|
+
to easily opt in to hooks. Take a look at how this repo uses nix-git-hooks in
|
|
157
|
+
[flake.nix](./flake.nix).
|
|
146
158
|
|
|
147
|
-
|
|
148
|
-
$ git add .husky/pre-commit
|
|
159
|
+
[nix-git-hooks]: https://github.com/ysndr/nix-git-hooks
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
patterns are quoted!
|
|
161
|
+
#### Npm, yarn, pnpm, or bun
|
|
152
162
|
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
[Husky][] will automatically install git hooks for all project collaborators
|
|
164
|
+
after running package manager commands.
|
|
165
|
+
|
|
166
|
+
[Husky]: https://typicode.github.io/husky/get-started.html
|
|
167
|
+
|
|
168
|
+
#### Rust with Cargo
|
|
169
|
+
|
|
170
|
+
[cargo-husky][] is like Husky, but for Rust projects.
|
|
171
|
+
|
|
172
|
+
[cargo-husky]: https://crates.io/crates/cargo-husky
|
|
173
|
+
|
|
174
|
+
#### and more!
|
|
175
|
+
|
|
176
|
+
See more hook management options in [awesome-git][] and [awesome-git-hooks][].
|
|
177
|
+
|
|
178
|
+
[awesome-git]: https://github.com/dictcp/awesome-git?tab=readme-ov-file#hook-management.
|
|
179
|
+
[awesome-git-hooks]: https://github.com/compscilauren/awesome-git-hooks?tab=readme-ov-file#tools
|
|
155
180
|
|
|
156
181
|
## Comparisons to similar utilities
|
|
157
182
|
|
package/git-format-staged
CHANGED
|
@@ -20,13 +20,14 @@ from fnmatch import fnmatch
|
|
|
20
20
|
from gettext import gettext as _
|
|
21
21
|
import re
|
|
22
22
|
import shlex
|
|
23
|
+
import signal
|
|
23
24
|
import subprocess
|
|
24
25
|
import sys
|
|
25
26
|
from typing import NoReturn, Protocol, cast
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
# The string 4.0.
|
|
29
|
-
VERSION = "4.0.
|
|
29
|
+
# The string 4.0.1 is replaced during the publish process.
|
|
30
|
+
VERSION = "4.0.1"
|
|
30
31
|
PROG = sys.argv[0]
|
|
31
32
|
|
|
32
33
|
|
|
@@ -34,6 +35,10 @@ def info(msg: str):
|
|
|
34
35
|
print(msg, file=sys.stdout)
|
|
35
36
|
|
|
36
37
|
|
|
38
|
+
def info_stderr(msg: str):
|
|
39
|
+
print(msg, file=sys.stderr)
|
|
40
|
+
|
|
41
|
+
|
|
37
42
|
def warn(msg: str):
|
|
38
43
|
print("{}: warning: {}".format(PROG, msg), file=sys.stderr)
|
|
39
44
|
|
|
@@ -144,31 +149,85 @@ def format_object(
|
|
|
144
149
|
get_content = subprocess.Popen(
|
|
145
150
|
["git", "cat-file", "-p", object_hash], stdout=subprocess.PIPE
|
|
146
151
|
)
|
|
152
|
+
|
|
147
153
|
command = re.sub(file_path_placeholder, shlex.quote(file_path), formatter)
|
|
148
154
|
if verbose:
|
|
149
|
-
|
|
155
|
+
info_stderr(command)
|
|
150
156
|
format_content = subprocess.Popen(
|
|
151
157
|
command, shell=True, stdin=get_content.stdout, stdout=subprocess.PIPE
|
|
152
158
|
)
|
|
159
|
+
|
|
153
160
|
write_object = subprocess.Popen(
|
|
154
161
|
["git", "hash-object", "-w", "--stdin"],
|
|
155
162
|
stdin=format_content.stdout,
|
|
156
163
|
stdout=subprocess.PIPE,
|
|
157
164
|
)
|
|
158
165
|
|
|
159
|
-
#
|
|
160
|
-
#
|
|
166
|
+
# Close the parent process reference to stdout, leaving only references in the child processes.
|
|
167
|
+
# This way if the downstream process terminates while format_content is still running,
|
|
168
|
+
# format_content will be terminated with a SIGPIPE signal.
|
|
169
|
+
format_content.stdout.close() # pyright: ignore[reportOptionalMemberAccess]
|
|
170
|
+
|
|
171
|
+
# On the other hand we don't close get_content.stdout() so that we can check if there is unread
|
|
172
|
+
# data left after the formatter has finished.
|
|
173
|
+
|
|
174
|
+
# Read output from the last process in the pipe, and block until that process has completed.
|
|
175
|
+
# It's important to block on the last process completing before waiting for the other sub
|
|
176
|
+
# processes to finish.
|
|
161
177
|
new_hash, _err = write_object.communicate()
|
|
162
178
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
# The first two pipe processes should have completed by now. Block to verify that we have exit
|
|
180
|
+
# statuses from them.
|
|
181
|
+
try:
|
|
182
|
+
# Use communicate() to check for any unread output from get_content.
|
|
183
|
+
get_content_unread_stdout, _ = get_content.communicate(timeout=5)
|
|
184
|
+
get_content.stdout.close() # pyright: ignore[reportOptionalMemberAccess]
|
|
185
|
+
format_content_exit_status = format_content.wait(timeout=5)
|
|
186
|
+
except subprocess.TimeoutExpired as exception:
|
|
187
|
+
raise Exception(
|
|
188
|
+
"the formatter command did not terminate as expected"
|
|
189
|
+
) from exception
|
|
190
|
+
|
|
191
|
+
# An error from format_content is most relevant to the user, so prioritize displaying this error
|
|
192
|
+
# message in case multiple things went wrong.
|
|
193
|
+
if format_content_exit_status != 0:
|
|
194
|
+
raise Exception(
|
|
195
|
+
f"formatter exited with non-zero status ({format_content_exit_status}) while processing {file_path}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# If the formatter exited before reading all input then get_content might have been terminated
|
|
199
|
+
# by a SIGPIPE signal. This is probably incorrect behavior from the formatter command, but is
|
|
200
|
+
# allowed by design (for now). So we emit a warning, but will not fail.
|
|
201
|
+
|
|
202
|
+
# If there was unread output from get_content that's an indication that the formatter command is
|
|
203
|
+
# probably not configured correctly. But program design allows the formatter command do do what
|
|
204
|
+
# it wants. So this is a warning, not a hard error.
|
|
205
|
+
#
|
|
206
|
+
# A SIGPIPE termination to get_content would also indicate unread output. This should not
|
|
207
|
+
# happen, but it doesn't hurt to check.
|
|
208
|
+
if len(get_content_unread_stdout) > 0 or get_content.returncode == -signal.SIGPIPE:
|
|
209
|
+
warn(
|
|
210
|
+
f"the formatter command exited before reading all content from {file_path}"
|
|
166
211
|
)
|
|
167
212
|
|
|
168
|
-
if
|
|
169
|
-
|
|
213
|
+
if get_content.returncode != 0 and get_content.returncode != -signal.SIGPIPE:
|
|
214
|
+
if verbose:
|
|
215
|
+
info_stderr(
|
|
216
|
+
f"non-zero exit status from `git cat-file -p {object_hash}`\n"
|
|
217
|
+
+ f"exit status: {get_content.returncode}\n"
|
|
218
|
+
+ f"file path: {file_path}\n"
|
|
219
|
+
)
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"unable to read file content for {file_path} from object database."
|
|
222
|
+
)
|
|
170
223
|
|
|
171
224
|
if write_object.returncode != 0:
|
|
225
|
+
if verbose:
|
|
226
|
+
info_stderr(
|
|
227
|
+
f"non-zero exit status from `git hash-object -w --stdin`\n"
|
|
228
|
+
+ f"exit status: {write_object.returncode}\n"
|
|
229
|
+
+ f"file path: {file_path}\n"
|
|
230
|
+
)
|
|
172
231
|
raise Exception("unable to write formatted content to object database")
|
|
173
232
|
|
|
174
233
|
return new_hash.decode("utf-8").rstrip()
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-format-staged",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "Git command to transform staged files according to a command that accepts file content on stdin and produces output on stdout.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "ava",
|
|
7
|
+
"prepare": "update-npm-deps-hash || true",
|
|
7
8
|
"prepublishOnly": "sed -i \"s/\\$VERSION/$npm_package_version/\" git-format-staged",
|
|
8
9
|
"semantic-release": "semantic-release"
|
|
9
10
|
},
|