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} -p1 -i
17 svn diff --diff-cmd=diff -x-U0 | {clang_format_diff} -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__
.format(clang_format_diff
="%(prog)s"),
41 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
47 help="apply edits to files instead of displaying a diff",
53 help="strip the smallest prefix containing P slashes",
59 help="custom pattern selecting file paths to reformat "
60 "(case sensitive, overrides -iregex)",
65 default
=r
".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp"
66 r
"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|s?vh?)",
67 help="custom pattern selecting file paths to reformat "
68 "(case insensitive, overridden by -regex)",
74 help="let clang-format sort include blocks",
80 help="be more verbose, ineffective without -i",
84 help="formatting style to apply (LLVM, GNU, Google, Chromium, "
85 "Microsoft, Mozilla, WebKit)",
89 help="The name of the predefined style used as a"
90 "fallback in case clang-format is invoked with"
91 "-style=file, but can not find the .clang-format"
96 default
="clang-format",
97 help="location of binary to use for clang-format",
99 args
= parser
.parse_args()
101 # Extract changed lines for each file.
104 for line
in sys
.stdin
:
105 match
= re
.search(r
"^\+\+\+\ (.*?/){%s}(\S*)" % args
.p
, line
)
107 filename
= match
.group(2)
111 if args
.regex
is not None:
112 if not re
.match("^%s$" % args
.regex
, filename
):
115 if not re
.match("^%s$" % args
.iregex
, filename
, re
.IGNORECASE
):
118 match
= re
.search(r
"^@@.*\+(\d+)(?:,(\d+))?", line
)
120 start_line
= int(match
.group(1))
123 line_count
= int(match
.group(2))
124 # The input is something like
128 # which means no lines were added.
131 # Also format lines range if line_count is 0 in case of deleting
132 # surrounding statements.
133 end_line
= start_line
135 end_line
+= line_count
- 1
136 lines_by_file
.setdefault(filename
, []).extend(
137 ["--lines", str(start_line
) + ":" + str(end_line
)]
140 # Reformat files containing changes in place.
142 for filename
, lines
in lines_by_file
.items():
143 if args
.i
and args
.verbose
:
144 print("Formatting {}".format(filename
))
145 command
= [args
.binary
, filename
]
148 if args
.sort_includes
:
149 command
.append("--sort-includes")
150 command
.extend(lines
)
152 command
.extend(["--style", args
.style
])
153 if args
.fallback_style
:
154 command
.extend(["--fallback-style", args
.fallback_style
])
157 p
= subprocess
.Popen(
159 stdout
=subprocess
.PIPE
,
161 stdin
=subprocess
.PIPE
,
162 universal_newlines
=True,
165 # Give the user more context when clang-format isn't
166 # found/isn't executable, etc.
168 'Failed to run "%s" - %s"' % (" ".join(command
), e
.strerror
)
171 stdout
, _stderr
= p
.communicate()
172 if p
.returncode
!= 0:
176 with
open(filename
) as f
:
178 formatted_code
= StringIO(stdout
).readlines()
179 diff
= difflib
.unified_diff(
184 "(before formatting)",
185 "(after formatting)",
187 diff_string
= "".join(diff
)
188 if len(diff_string
) > 0:
190 sys
.stdout
.write(diff_string
)
196 if __name__
== "__main__":