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 HEAD^ | clang-format-diff.py -p1 -i
17 svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
20 from __future__ import absolute_import, division, print_function
28 if sys.version_info.major >= 3:
29 from io import StringIO
31 from io import BytesIO as StringIO
35 parser = argparse.ArgumentParser(
36 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
42 help="apply edits to files instead of displaying a " "diff",
48 help="strip the smallest prefix containing P slashes",
54 help="custom pattern selecting file paths to reformat "
55 "(case sensitive, overrides -iregex)",
60 default=r".*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|m|mm|inc"
61 r"|js|ts|proto|protodevel|java|cs)",
62 help="custom pattern selecting file paths to reformat "
63 "(case insensitive, overridden by -regex)",
69 help="let clang-format sort include blocks",
75 help="be more verbose, ineffective without -i",
79 help="formatting style to apply (LLVM, Google, " "Chromium, Mozilla, WebKit)",
83 default="clang-format",
84 help="location of binary to use for clang-format",
86 args = parser.parse_args()
88 # Extract changed lines for each file.
91 for line in sys.stdin:
92 match = re.search(r"^\+\+\+\ (.*?/){%s}(\S*)" % args.p, line)
94 filename = match.group(2)
98 if args.regex is not None:
99 if not re.match("^%s$" % args.regex, filename):
102 if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE):
105 match = re.search(r"^@@.*\+(\d+)(,(\d+))?", line)
107 start_line = int(match.group(1))
110 line_count = int(match.group(3))
113 end_line = start_line + line_count - 1
114 lines_by_file.setdefault(filename, []).extend(
115 ["-lines", str(start_line) + ":" + str(end_line)]
118 # Reformat files containing changes in place.
119 # We need to count amount of bytes generated in the output of
120 # clang-format-diff. If clang-format-diff doesn't generate any bytes it
121 # means there is nothing to format.
122 format_line_counter = 0
123 for filename, lines in lines_by_file.items():
124 if args.i and args.verbose:
125 print("Formatting {}".format(filename))
126 command = [args.binary, filename]
129 if args.sort_includes:
130 command.append("-sort-includes")
131 command.extend(lines)
133 command.extend(["-style", args.style])
134 p = subprocess.Popen(
136 stdout=subprocess.PIPE,
138 stdin=subprocess.PIPE,
139 universal_newlines=True,
141 stdout, _ = p.communicate()
142 if p.returncode != 0:
143 sys.exit(p.returncode)
146 with open(filename) as f:
148 formatted_code = StringIO(stdout).readlines()
149 diff = difflib.unified_diff(
154 "(before formatting)",
155 "(after formatting)",
157 diff_string = "".join(diff)
159 format_line_counter += sys.stdout.write(diff_string)
161 if format_line_counter > 0:
165 if __name__ == "__main__":