git-format-staged 3.1.1 → 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 CHANGED
@@ -1,7 +1,5 @@
1
1
  # git-format-staged
2
2
 
3
- [![Build Status](https://travis-ci.org/hallettj/git-format-staged.svg?branch=master)](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 add github:hallettj/git-format-staged
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 version 3 or 2.7.
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 version 3 or 2.7.
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 "{}"' 'src/*.js'
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 "{}"' 'src/*.js' 'src/*.jsx' 'src/*.ts'
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 "{}"' '*.js' '!flow-typed/*'
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. This is useful if your
108
- formatter needs to know the file extension to determine how to format or to
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 "{}"' '*.js' '*.css'
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 "{}" >&2' 'src/*.js'
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 \"{}\"' '*.js' '*.ts'"
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 "{}"' '*.js'
10
+ # Example: git-format-staged --formatter 'prettier --stdin-filepath {}' '*.js'
11
11
  #
12
- # Tested with Python 3.10 and Python 2.7.
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.1 is replaced during the publish process.
26
- VERSION = '3.1.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
- output = subprocess.check_output([
42
- 'git', 'diff-index',
43
- '--cached',
44
- '--diff-filter=AM', # select only file additions and modifications
45
- '--no-renames',
46
- 'HEAD'
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
- file_path_placeholder = re.compile(r'\{\}')
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
- get_content.stdout.close()
114
- format_content.stdout.close()
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') # TODO: capture stderr from format command
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')
@@ -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 \'{}\'" "src/*.js" "test/*.js"'
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 \'{}\' >&2" "*.js"'
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.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": "^8.3.5",
33
- "@commitlint/config-conventional": "^8.3.4",
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": "^4.2.5",
39
+ "husky": "^9.0.11",
40
40
  "micromatch": "^4.0.2",
41
41
  "prettier-standard": "^9.1.1",
42
- "semantic-release": "^19.0.2",
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",