Skip to content

Commit c9b4e74

Browse files
committed
Updated git-format-staged
1 parent 4950f3c commit c9b4e74

File tree

1 file changed

+32
-19
lines changed

1 file changed

+32
-19
lines changed

Scripts/git-format-staged.py

+32-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python3
22
#
33
# Git command to transform staged files according to a command that accepts file
44
# content on stdin and produces output on stdout. This command is useful in
@@ -7,27 +7,28 @@
77
# ignoring unstaged changes.
88
#
99
# Usage: git-format-staged [OPTION]... [FILE]...
10-
# Example: git-format-staged --formatter 'prettier --stdin' '*.js'
10+
# Example: git-format-staged --formatter 'prettier --stdin-filepath "{}"' '*.js'
1111
#
12-
# Tested with Python 3.6 and Python 2.7.
12+
# Tested with Python versions 3.8 - 3.13.
1313
#
1414
# Original author: Jesse Hallett <[email protected]>
1515

1616
from __future__ import print_function
17+
1718
import argparse
18-
from fnmatch import fnmatch
19-
from gettext import gettext as _
2019
import os
2120
import re
2221
import subprocess
2322
import sys
23+
from fnmatch import fnmatch
24+
from gettext import gettext as _
2425

2526
# The string $VERSION is replaced during the publish process.
2627
VERSION = '$VERSION'
2728
PROG = sys.argv[0]
2829

2930
def info(msg):
30-
print(msg, file=sys.stderr)
31+
print(msg, file=sys.stdout)
3132

3233
def warn(msg):
3334
print('{}: warning: {}'.format(PROG, msg), file=sys.stderr)
@@ -36,7 +37,7 @@ def fatal(msg):
3637
print('{}: error: {}'.format(PROG, msg), file=sys.stderr)
3738
exit(1)
3839

39-
def format_staged_files(file_patterns, formatter, git_root, update_working_tree=True, write=True):
40+
def format_staged_files(file_patterns, formatter, git_root, update_working_tree=True, write=True, verbose=False):
4041
try:
4142
output = subprocess.check_output([
4243
'git', 'diff-index',
@@ -48,19 +49,22 @@ def format_staged_files(file_patterns, formatter, git_root, update_working_tree=
4849
for line in output.splitlines():
4950
entry = parse_diff(line.decode('utf-8'))
5051
entry_path = normalize_path(entry['src_path'], relative_to=git_root)
52+
if entry['dst_mode'] == '120000':
53+
# Do not process symlinks
54+
continue
5155
if not (matches_some_path(file_patterns, entry_path)):
5256
continue
53-
if format_file_in_index(formatter, entry, update_working_tree=update_working_tree, write=write):
57+
if format_file_in_index(formatter, entry, update_working_tree=update_working_tree, write=write, verbose=verbose):
5458
info('Reformatted {} with {}'.format(entry['src_path'], formatter))
5559
except Exception as err:
5660
fatal(str(err))
5761

5862
# Run formatter on file in the git index. Creates a new git object with the
5963
# result, and replaces the content of the file in the index with that object.
6064
# Returns hash of the new object if formatting produced any changes.
61-
def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=True):
65+
def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=True, verbose=False):
6266
orig_hash = diff_entry['dst_hash']
63-
new_hash = format_object(formatter, orig_hash, diff_entry['src_path'])
67+
new_hash = format_object(formatter, orig_hash, diff_entry['src_path'], verbose=verbose)
6468

6569
# If the new hash is the same then the formatter did not make any changes.
6670
if not write or new_hash == orig_hash:
@@ -83,17 +87,20 @@ def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=
8387

8488
return new_hash
8589

86-
file_path_placeholder = re.compile('\{\}')
90+
file_path_placeholder = re.compile(r'\{\}')
8791

8892
# Run formatter on a git blob identified by its hash. Writes output to a new git
8993
# blob, and returns the hash of the new blob.
90-
def format_object(formatter, object_hash, file_path):
94+
def format_object(formatter, object_hash, file_path, verbose=False):
9195
get_content = subprocess.Popen(
9296
['git', 'cat-file', '-p', object_hash],
9397
stdout=subprocess.PIPE
9498
)
99+
command = re.sub(file_path_placeholder, file_path, formatter)
100+
if verbose:
101+
info(command)
95102
format_content = subprocess.Popen(
96-
re.sub(file_path_placeholder, file_path, formatter),
103+
command,
97104
shell=True,
98105
stdin=get_content.stdout,
99106
stdout=subprocess.PIPE
@@ -142,7 +149,7 @@ def replace_file_in_index(diff_entry, new_object_hash):
142149

143150
def patch_working_file(path, orig_object_hash, new_object_hash):
144151
patch = subprocess.check_output(
145-
['git', 'diff', orig_object_hash, new_object_hash]
152+
['git', 'diff', '--no-ext-diff', '--color=never', orig_object_hash, new_object_hash]
146153
)
147154

148155
# Substitute object hashes in patch header with path to working tree file
@@ -161,7 +168,7 @@ def patch_working_file(path, orig_object_hash, new_object_hash):
161168
raise Exception('could not apply formatting changes to working tree file {}'.format(path))
162169

163170
# Format: src_mode dst_mode src_hash dst_hash status/score? src_path dst_path?
164-
diff_pat = re.compile('^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$')
171+
diff_pat = re.compile(r'^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$')
165172

166173
# Parse output from `git diff-index`
167174
def parse_diff(diff):
@@ -179,7 +186,7 @@ def parse_diff(diff):
179186
'dst_path': m.group(8)
180187
}
181188

182-
zeroed_pat = re.compile('^0+$')
189+
zeroed_pat = re.compile(r'^0+$')
183190

184191
# Returns the argument unless the argument is a string of zeroes, in which case
185192
# returns `None`
@@ -228,12 +235,12 @@ def parse_args(self, args=None, namespace=None):
228235
if __name__ == '__main__':
229236
parser = CustomArgumentParser(
230237
description='Transform staged files using a formatting command that accepts content via stdin and produces a result via stdout.',
231-
epilog='Example: %(prog)s --formatter "prettier --stdin" "src/*.js" "test/*.js"'
238+
epilog='Example: %(prog)s --formatter "prettier --stdin-filepath \'{}\'" "src/*.js" "test/*.js"'
232239
)
233240
parser.add_argument(
234241
'--formatter', '-f',
235242
required=True,
236-
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 --stdin-filepath \'{}\'")'
243+
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 \'{}\'")'
237244
)
238245
parser.add_argument(
239246
'--no-update-working-tree',
@@ -251,6 +258,11 @@ def parse_args(self, args=None, namespace=None):
251258
version='%(prog)s version {}'.format(VERSION),
252259
help='Display version of %(prog)s'
253260
)
261+
parser.add_argument(
262+
'--verbose',
263+
help='Show the formatting commands that are running',
264+
action='store_true'
265+
)
254266
parser.add_argument(
255267
'files',
256268
nargs='+',
@@ -263,5 +275,6 @@ def parse_args(self, args=None, namespace=None):
263275
formatter=vars(args)['formatter'],
264276
git_root=get_git_root(),
265277
update_working_tree=not vars(args)['no_update_working_tree'],
266-
write=not vars(args)['no_write']
278+
write=not vars(args)['no_write'],
279+
verbose=vars(args)['verbose']
267280
)

0 commit comments

Comments
 (0)