3 # ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
5 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6 # See https://llvm.org/LICENSE.txt for license information.
7 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
9 # ===------------------------------------------------------------------------===#
12 This script reads input from a unified diff and reformats all the changed
13 lines. This is useful to reformat all the lines touched by a specific patch.
14 Example usage for git/svn users:
16 git diff -U0 --no-color --relative HEAD^ | clang-format-diff.py -p1 -i
17 svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
19 It should be noted that the filename contained in the diff is used unmodified
20 to determine the source file to update. Users calling this script directly
21 should be careful to ensure that the path in the diff is correct relative to the
22 current working directory.
24 from __future__
import absolute_import
, division
, print_function
32 if sys
.version_info
.major
>= 3:
33 from io
import StringIO
35 from io
import BytesIO
as StringIO
39 parser
= argparse
.ArgumentParser(
40 description
=__doc__
, formatter_class
=argparse
.RawDescriptionHelpFormatter
46 help="apply edits to files instead of displaying a diff",
52 help="strip the smallest prefix containing P slashes",
58 help="custom pattern selecting file paths to reformat "
59 "(case sensitive, overrides -iregex)",
64 default
=r
".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp"
65 r
"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|s?vh?)",
66 help="custom pattern selecting file paths to reformat "
67 "(case insensitive, overridden by -regex)",
73 help="let clang-format sort include blocks",
79 help="be more verbose, ineffective without -i",
83 help="formatting style to apply (LLVM, GNU, Google, Chromium, "
84 "Microsoft, Mozilla, WebKit)",
88 help="The name of the predefined style used as a"
89 "fallback in case clang-format is invoked with"
90 "-style=file, but can not find the .clang-format"
95 default
="clang-format",
96 help="location of binary to use for clang-format",
98 args
= parser
.parse_args()
100 # Extract changed lines for each file.
103 for line
in sys
.stdin
:
104 match
= re
.search(r
"^\+\+\+\ (.*?/){%s}(\S*)" % args
.p
, line
)
106 filename
= match
.group(2)
110 if args
.regex
is not None:
111 if not re
.match("^%s$" % args
.regex
, filename
):
114 if not re
.match("^%s$" % args
.iregex
, filename
, re
.IGNORECASE
):
117 match
= re
.search(r
"^@@.*\+(\d+)(?:,(\d+))?", line
)
119 start_line
= int(match
.group(1))
122 line_count
= int(match
.group(2))
123 # The input is something like
127 # which means no lines were added.
130 # Also format lines range if line_count is 0 in case of deleting
131 # surrounding statements.
132 end_line
= start_line
134 end_line
+= line_count
- 1
135 lines_by_file
.setdefault(filename
, []).extend(
136 ["-lines", str(start_line
) + ":" + str(end_line
)]
139 # Reformat files containing changes in place.
140 for filename
, lines
in lines_by_file
.items():
141 if args
.i
and args
.verbose
:
142 print("Formatting {}".format(filename
))
143 command
= [args
.binary
, filename
]
146 if args
.sort_includes
:
147 command
.append("-sort-includes")
148 command
.extend(lines
)
150 command
.extend(["-style", args
.style
])
151 if args
.fallback_style
:
152 command
.extend(["-fallback-style", args
.fallback_style
])
155 p
= subprocess
.Popen(
157 stdout
=subprocess
.PIPE
,
159 stdin
=subprocess
.PIPE
,
160 universal_newlines
=True,
163 # Give the user more context when clang-format isn't
164 # found/isn't executable, etc.
166 'Failed to run "%s" - %s"' % (" ".join(command
), e
.strerror
)
169 stdout
, stderr
= p
.communicate()
170 if p
.returncode
!= 0:
171 sys
.exit(p
.returncode
)
174 with
open(filename
) as f
:
176 formatted_code
= StringIO(stdout
).readlines()
177 diff
= difflib
.unified_diff(
182 "(before formatting)",
183 "(after formatting)",
185 diff_string
= "".join(diff
)
186 if len(diff_string
) > 0:
187 sys
.stdout
.write(diff_string
)
190 if __name__
== "__main__":