git-format-staged 3.1.0 → 3.1.2
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 +12 -14
- package/git-format-staged +35 -23
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# git-format-staged
|
|
2
2
|
|
|
3
|
-
[](https://travis-ci.org/hallettj/git-format-staged)
|
|
4
|
-
|
|
5
3
|
Consider a project where you want all code formatted consistently. So you use
|
|
6
4
|
a formatting command. (For example I use [prettier][] in my Javascript and
|
|
7
5
|
Typescript projects.) You want to make sure that everyone working on the project
|
|
@@ -39,13 +37,13 @@ patch step can be disabled with the `--no-update-working-tree` option.
|
|
|
39
37
|
|
|
40
38
|
Install via the CLI:
|
|
41
39
|
|
|
42
|
-
$ nix profile
|
|
40
|
+
$ nix profile install github:hallettj/git-format-staged
|
|
43
41
|
|
|
44
42
|
Or add to your flake imports, and use the `default` package output.
|
|
45
43
|
|
|
46
44
|
### Install with NPM
|
|
47
45
|
|
|
48
|
-
Requires Python
|
|
46
|
+
Requires Python 3.8 or later.
|
|
49
47
|
|
|
50
48
|
Install as a development dependency in a project that uses npm packages:
|
|
51
49
|
|
|
@@ -57,7 +55,7 @@ Or install globally:
|
|
|
57
55
|
|
|
58
56
|
### Or just copy the script
|
|
59
57
|
|
|
60
|
-
Requires Python
|
|
58
|
+
Requires Python 3.8 or later.
|
|
61
59
|
|
|
62
60
|
If you do not use the above methods you can copy the
|
|
63
61
|
[`git-format-staged`](./git-format-staged) script from this repository and
|
|
@@ -73,7 +71,7 @@ For detailed information run:
|
|
|
73
71
|
The command expects a shell command to run a formatter, and one or more file
|
|
74
72
|
patterns to identify which files should be formatted. For example:
|
|
75
73
|
|
|
76
|
-
$ git-format-staged --formatter 'prettier --stdin-filepath
|
|
74
|
+
$ git-format-staged --formatter 'prettier --stdin-filepath {}' 'src/*.js'
|
|
77
75
|
|
|
78
76
|
That will format all files under `src/` and its subdirectories using
|
|
79
77
|
`prettier`. The file pattern is tested against staged files using Python's
|
|
@@ -90,11 +88,11 @@ normal shell globbing. So if you need to match multiple patterns, you should
|
|
|
90
88
|
pass multiple arguments with different patterns, and they will be grouped.
|
|
91
89
|
So instead of e.g. `'src/**/*.{js,jsx,ts}'`, you would use:
|
|
92
90
|
|
|
93
|
-
$ git-format-staged --formatter 'prettier --stdin-filepath
|
|
91
|
+
$ git-format-staged --formatter 'prettier --stdin-filepath {}' 'src/*.js' 'src/*.jsx' 'src/*.ts'
|
|
94
92
|
|
|
95
93
|
Files can be excluded by prefixing a pattern with `!`. For example:
|
|
96
94
|
|
|
97
|
-
$ git-format-staged --formatter 'prettier --stdin-filepath
|
|
95
|
+
$ git-format-staged --formatter 'prettier --stdin-filepath {}' '*.js' '!flow-typed/*'
|
|
98
96
|
|
|
99
97
|
Patterns are evaluated from left-to-right: if a file matches multiple patterns
|
|
100
98
|
the right-most pattern determines whether the file is included or excluded.
|
|
@@ -104,11 +102,11 @@ control. So it is not necessary to explicitly exclude stuff like
|
|
|
104
102
|
`node_modules/`.
|
|
105
103
|
|
|
106
104
|
The formatter command may include a placeholder, `{}`, which will be replaced
|
|
107
|
-
with the path of the file that is being formatted
|
|
108
|
-
formatter needs to know the file extension to determine
|
|
109
|
-
lint each file. For example:
|
|
105
|
+
with the path of the file that is being formatted (with appropriate quoting).
|
|
106
|
+
This is useful if your formatter needs to know the file extension to determine
|
|
107
|
+
how to format or to lint each file. For example:
|
|
110
108
|
|
|
111
|
-
$ git-format-staged -f 'prettier --stdin-filepath
|
|
109
|
+
$ git-format-staged -f 'prettier --stdin-filepath {}' '*.js' '*.css'
|
|
112
110
|
|
|
113
111
|
Do not attempt to read or write to `{}` in your formatter command! The
|
|
114
112
|
placeholder exists only for referencing the file name and path.
|
|
@@ -120,7 +118,7 @@ prevent files from being committed if they do not conform to style rules. You
|
|
|
120
118
|
can use git-format-staged with the `--no-write` option, and supply a lint
|
|
121
119
|
command instead of a format command. Here is an example using ESLint:
|
|
122
120
|
|
|
123
|
-
$ git-format-staged --no-write -f 'eslint --stdin --stdin-filename
|
|
121
|
+
$ git-format-staged --no-write -f 'eslint --stdin --stdin-filename {} >&2' 'src/*.js'
|
|
124
122
|
|
|
125
123
|
If this command is run in a pre-commit hook, and the lint command fails the
|
|
126
124
|
commit will be aborted and error messages will be displayed. The lint command
|
|
@@ -146,7 +144,7 @@ Add a `prepare` script to install husky when running `npm install`:
|
|
|
146
144
|
|
|
147
145
|
Add the pre-commit hook:
|
|
148
146
|
|
|
149
|
-
$ npx husky add .husky/pre-commit "git-format-staged --formatter 'prettier --stdin-filepath
|
|
147
|
+
$ npx husky add .husky/pre-commit "git-format-staged --formatter 'prettier --stdin-filepath {}' '*.js' '*.ts'"
|
|
150
148
|
$ git add .husky/pre-commit
|
|
151
149
|
|
|
152
150
|
Once again note that the formatter command and the `'*.js'` and `'*.ts'`
|
package/git-format-staged
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
# ignoring unstaged changes.
|
|
8
8
|
#
|
|
9
9
|
# Usage: git-format-staged [OPTION]... [FILE]...
|
|
10
|
-
# Example: git-format-staged --formatter 'prettier --stdin-filepath
|
|
10
|
+
# Example: git-format-staged --formatter 'prettier --stdin-filepath {}' '*.js'
|
|
11
11
|
#
|
|
12
|
-
# Tested with Python 3.
|
|
12
|
+
# Tested with Python versions 3.8 - 3.15.
|
|
13
13
|
#
|
|
14
14
|
# Original author: Jesse Hallett <jesse@sitr.us>
|
|
15
15
|
|
|
@@ -19,11 +19,12 @@ from fnmatch import fnmatch
|
|
|
19
19
|
from gettext import gettext as _
|
|
20
20
|
import os
|
|
21
21
|
import re
|
|
22
|
+
import shlex
|
|
22
23
|
import subprocess
|
|
23
24
|
import sys
|
|
24
25
|
|
|
25
|
-
# The string 3.1.
|
|
26
|
-
VERSION = '3.1.
|
|
26
|
+
# The string 3.1.2 is replaced during the publish process.
|
|
27
|
+
VERSION = '3.1.2'
|
|
27
28
|
PROG = sys.argv[0]
|
|
28
29
|
|
|
29
30
|
def info(msg):
|
|
@@ -37,14 +38,20 @@ def fatal(msg):
|
|
|
37
38
|
exit(1)
|
|
38
39
|
|
|
39
40
|
def format_staged_files(file_patterns, formatter, git_root, update_working_tree=True, write=True, verbose=False):
|
|
41
|
+
common_opts = [
|
|
42
|
+
'--cached',
|
|
43
|
+
'--diff-filter=AM', # select only file additions and modifications
|
|
44
|
+
'--no-renames',
|
|
45
|
+
'HEAD',
|
|
46
|
+
]
|
|
47
|
+
|
|
40
48
|
try:
|
|
41
|
-
|
|
42
|
-
'
|
|
43
|
-
'--
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
])
|
|
49
|
+
staged_files = {
|
|
50
|
+
path.decode('utf-8')
|
|
51
|
+
for path in subprocess.check_output(['git', 'diff', '--name-only'] + common_opts).splitlines()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
output = subprocess.check_output(['git', 'diff-index'] + common_opts)
|
|
48
55
|
for line in output.splitlines():
|
|
49
56
|
entry = parse_diff(line.decode('utf-8'))
|
|
50
57
|
entry_path = normalize_path(entry['src_path'], relative_to=git_root)
|
|
@@ -53,6 +60,9 @@ def format_staged_files(file_patterns, formatter, git_root, update_working_tree=
|
|
|
53
60
|
continue
|
|
54
61
|
if not (matches_some_path(file_patterns, entry_path)):
|
|
55
62
|
continue
|
|
63
|
+
if entry['src_mode'] is None and object_is_empty(entry['dst_hash']) and entry['src_path'] not in staged_files:
|
|
64
|
+
# File is not staged, it's tracked only with `--intent-to-add` and won't get committed
|
|
65
|
+
continue
|
|
56
66
|
if format_file_in_index(formatter, entry, update_working_tree=update_working_tree, write=write, verbose=verbose):
|
|
57
67
|
info('Reformatted {} with {}'.format(entry['src_path'], formatter))
|
|
58
68
|
except Exception as err:
|
|
@@ -86,7 +96,10 @@ def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=
|
|
|
86
96
|
|
|
87
97
|
return new_hash
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
# Match {}, and to avoid breaking quoting from shlex also match and remove surrounding quotes. This
|
|
100
|
+
# is important for backward compatibility because previous version of git-format-staged did not use
|
|
101
|
+
# shlex quoting, and required manual quoting.
|
|
102
|
+
file_path_placeholder = re.compile(r"(['\"]?)\{\}(\1)")
|
|
90
103
|
|
|
91
104
|
# Run formatter on a git blob identified by its hash. Writes output to a new git
|
|
92
105
|
# blob, and returns the hash of the new blob.
|
|
@@ -95,7 +108,7 @@ def format_object(formatter, object_hash, file_path, verbose=False):
|
|
|
95
108
|
['git', 'cat-file', '-p', object_hash],
|
|
96
109
|
stdout=subprocess.PIPE
|
|
97
110
|
)
|
|
98
|
-
command = re.sub(file_path_placeholder, file_path, formatter)
|
|
111
|
+
command = re.sub(file_path_placeholder, shlex.quote(file_path), formatter)
|
|
99
112
|
if verbose:
|
|
100
113
|
info(command)
|
|
101
114
|
format_content = subprocess.Popen(
|
|
@@ -110,16 +123,15 @@ def format_object(formatter, object_hash, file_path, verbose=False):
|
|
|
110
123
|
stdout=subprocess.PIPE
|
|
111
124
|
)
|
|
112
125
|
|
|
113
|
-
|
|
114
|
-
|
|
126
|
+
# Read output from the final process first to avoid deadlock if intermediate processes produce
|
|
127
|
+
# large outputs that would fill pipe buffers
|
|
128
|
+
new_hash, _ = write_object.communicate()
|
|
115
129
|
|
|
116
130
|
if get_content.wait() != 0:
|
|
117
131
|
raise ValueError('unable to read file content from object database: ' + object_hash)
|
|
118
132
|
|
|
119
133
|
if format_content.wait() != 0:
|
|
120
|
-
raise Exception('formatter exited with non-zero status')
|
|
121
|
-
|
|
122
|
-
new_hash, err = write_object.communicate()
|
|
134
|
+
raise Exception('formatter exited with non-zero status')
|
|
123
135
|
|
|
124
136
|
if write_object.returncode != 0:
|
|
125
137
|
raise Exception('unable to write formatted content to object database')
|
|
@@ -167,7 +179,7 @@ def patch_working_file(path, orig_object_hash, new_object_hash):
|
|
|
167
179
|
raise Exception('could not apply formatting changes to working tree file {}'.format(path))
|
|
168
180
|
|
|
169
181
|
# Format: src_mode dst_mode src_hash dst_hash status/score? src_path dst_path?
|
|
170
|
-
diff_pat = re.compile('^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$')
|
|
182
|
+
diff_pat = re.compile(r'^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$')
|
|
171
183
|
|
|
172
184
|
# Parse output from `git diff-index`
|
|
173
185
|
def parse_diff(diff):
|
|
@@ -185,7 +197,7 @@ def parse_diff(diff):
|
|
|
185
197
|
'dst_path': m.group(8)
|
|
186
198
|
}
|
|
187
199
|
|
|
188
|
-
zeroed_pat = re.compile('^0+$')
|
|
200
|
+
zeroed_pat = re.compile(r'^0+$')
|
|
189
201
|
|
|
190
202
|
# Returns the argument unless the argument is a string of zeroes, in which case
|
|
191
203
|
# returns `None`
|
|
@@ -234,12 +246,12 @@ class CustomArgumentParser(argparse.ArgumentParser):
|
|
|
234
246
|
if __name__ == '__main__':
|
|
235
247
|
parser = CustomArgumentParser(
|
|
236
248
|
description='Transform staged files using a formatting command that accepts content via stdin and produces a result via stdout.',
|
|
237
|
-
epilog='Example: %(prog)s --formatter "prettier --stdin-filepath
|
|
249
|
+
epilog='Example: %(prog)s --formatter "prettier --stdin-filepath {}" "src/*.js" "test/*.js"'
|
|
238
250
|
)
|
|
239
251
|
parser.add_argument(
|
|
240
252
|
'--formatter', '-f',
|
|
241
253
|
required=True,
|
|
242
|
-
help='Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted. (Example: "prettier --stdin-filepath
|
|
254
|
+
help='Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted (with appropriate quoting). (Example: "prettier --stdin-filepath {}")'
|
|
243
255
|
)
|
|
244
256
|
parser.add_argument(
|
|
245
257
|
'--no-update-working-tree',
|
|
@@ -249,7 +261,7 @@ if __name__ == '__main__':
|
|
|
249
261
|
parser.add_argument(
|
|
250
262
|
'--no-write',
|
|
251
263
|
action='store_true',
|
|
252
|
-
help='Prevents %(prog)s from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored. Example: %(prog)s --no-write -f "eslint --stdin --stdin-filename
|
|
264
|
+
help='Prevents %(prog)s from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored. Example: %(prog)s --no-write -f "eslint --stdin --stdin-filename {} >&2" "*.js"'
|
|
253
265
|
)
|
|
254
266
|
parser.add_argument(
|
|
255
267
|
'--version',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-format-staged",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
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",
|
|
@@ -29,17 +29,17 @@
|
|
|
29
29
|
"git-format-staged"
|
|
30
30
|
],
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@commitlint/cli": "^
|
|
33
|
-
"@commitlint/config-conventional": "^
|
|
32
|
+
"@commitlint/cli": "^19.0.3",
|
|
33
|
+
"@commitlint/config-conventional": "^19.0.3",
|
|
34
34
|
"@types/fs-extra": "^8.1.0",
|
|
35
35
|
"@types/tmp": "^0.1.0",
|
|
36
36
|
"ava": "^3.8.1",
|
|
37
37
|
"eslint": "^5.16.0",
|
|
38
38
|
"fs-extra": "^9.0.0",
|
|
39
|
-
"husky": "^
|
|
39
|
+
"husky": "^9.0.11",
|
|
40
40
|
"micromatch": "^4.0.2",
|
|
41
41
|
"prettier-standard": "^9.1.1",
|
|
42
|
-
"semantic-release": "^
|
|
42
|
+
"semantic-release": "^23.0.2",
|
|
43
43
|
"strip-indent": "^3.0.0",
|
|
44
44
|
"tmp": "0.2.0",
|
|
45
45
|
"ts-node": "^8.9.1",
|