|
5 | 5 | import logging
|
6 | 6 | import os
|
7 | 7 | import subprocess
|
| 8 | +import re |
8 | 9 |
|
9 | 10 | from functools import reduce
|
10 | 11 |
|
|
16 | 17 | # Fill me
|
17 | 18 | ]
|
18 | 19 |
|
| 20 | +DIFF_FILE_PATT = re.compile(r'^\+\+\+ b\/(.*)') |
| 21 | +DIFF_HUNK_PATT = re.compile(r'^@@ \-\d+(,\d+)? \+(\d+)(,)?(\d+)? @@.*') |
| 22 | + |
19 | 23 | ## ==============================================
|
20 | 24 | ## LOGGING CONFIGURATION
|
21 | 25 | ## ==============================================
|
@@ -44,21 +48,20 @@ def find_clangformat():
|
44 | 48 | CLANG_FORMAT = find_clangformat()
|
45 | 49 | CLANG_COMMAND_PREFIX = [CLANG_FORMAT, "-style=file"]
|
46 | 50 |
|
47 |
| -def clang_check(file_path): |
| 51 | +def clang_check(file_path, hunks=None): |
48 | 52 | """Checks and reports bad code formatting."""
|
| 53 | + |
| 54 | + assert not file_path is None and not file_path == "" |
| 55 | + |
49 | 56 | rel_path_from_peloton_dir = os.path.relpath(file_path, PELOTON_DIR)
|
50 | 57 |
|
51 | 58 | if rel_path_from_peloton_dir in FORMATTING_FILE_WHITELIST:
|
52 | 59 | return True
|
53 | 60 |
|
54 | 61 | file_status = True
|
55 | 62 |
|
56 |
| - # Run clang-format on the file |
57 |
| - if CLANG_FORMAT is None: |
58 |
| - LOG.error("clang-format seems not installed") |
59 |
| - exit() |
60 |
| - clang_format_cmd = CLANG_COMMAND_PREFIX + [file_path] |
61 |
| - formatted_src = subprocess.check_output(clang_format_cmd).splitlines(True) |
| 63 | + # Run clang-format on the file and get output (not inline!) |
| 64 | + formatted_src = clang_format(file_path, None, inline=False) |
62 | 65 |
|
63 | 66 | # For Python 3, the above command gives a list of binary sequences, each
|
64 | 67 | # of which has to be converted to string for diff to operate correctly.
|
@@ -86,12 +89,84 @@ def clang_check(file_path):
|
86 | 89 | return file_status
|
87 | 90 |
|
88 | 91 |
|
89 |
| -def clang_format(file_path): |
90 |
| - """Formats the file at file_path""" |
| 92 | +def clang_format(file_path, hunks=None, inline=True): |
| 93 | + """Formats the file at file_path. |
| 94 | + 'hunks' can be a list of pairs with (start,end) line numbers, 1 based. |
| 95 | + """ |
| 96 | + |
| 97 | + assert not file_path is None and not file_path == "" |
| 98 | + |
91 | 99 | if CLANG_FORMAT is None:
|
92 | 100 | LOG.error("clang-format seems not installed")
|
93 | 101 | exit()
|
94 | 102 |
|
95 |
| - formatting_command = CLANG_COMMAND_PREFIX + ["-i", file_path] |
| 103 | + formatting_command = CLANG_COMMAND_PREFIX + [file_path] |
| 104 | + |
| 105 | + if inline: |
| 106 | + formatting_command.append("-i") |
| 107 | + |
| 108 | + if not hunks is None: |
| 109 | + for start, end in hunks: |
| 110 | + if start > 0 and end > 0: |
| 111 | + formatting_command.append("-lines={}:{}".format(start, end)) |
| 112 | + |
96 | 113 | LOG.info(' '.join(formatting_command))
|
97 |
| - subprocess.call(formatting_command) |
| 114 | + output = subprocess.check_output(formatting_command).splitlines(True) |
| 115 | + return output |
| 116 | + |
| 117 | + |
| 118 | +def hunks_from_last_commits(n): |
| 119 | + """ Extract hunks of the last n commits. """ |
| 120 | + |
| 121 | + assert n > 0 |
| 122 | + |
| 123 | + diff_output = subprocess.check_output(["git", "diff", "HEAD~"+str(n) , "--diff-filter=d", "--unified=0"] |
| 124 | + ).decode("utf-8").splitlines() |
| 125 | + |
| 126 | + return _hunks_from_diff(diff_output) |
| 127 | + |
| 128 | + |
| 129 | +def hunks_from_staged_files(): |
| 130 | + diff_output = subprocess.check_output(["git", "diff", "HEAD", |
| 131 | + "--cached", "--diff-filter=d", "--unified=0"] |
| 132 | + ).decode("utf-8").splitlines() |
| 133 | + |
| 134 | + return _hunks_from_diff(diff_output) |
| 135 | + |
| 136 | + |
| 137 | +def _hunks_from_diff(diff_output): |
| 138 | + """ Parse a diff output and extract the hunks of changed files. |
| 139 | + The diff output must not have additional lines! |
| 140 | + (use --unified=0) """ |
| 141 | + |
| 142 | + # TARGETS is a list of files with an optional list of hunks, represented as |
| 143 | + # pair (start, end) of line numbers, 1 based. |
| 144 | + # element of TARGETS: (filename, None) or (filename, [(start,end)]) |
| 145 | + target_files = [] |
| 146 | + |
| 147 | + # hunks_current_list serves as a reference to the hunks list of the |
| 148 | + # last added file |
| 149 | + hunks_current_list = None |
| 150 | + |
| 151 | + for line in diff_output: |
| 152 | + file_match = DIFF_FILE_PATT.search(line) |
| 153 | + hunk_match = DIFF_HUNK_PATT.search(line) |
| 154 | + if file_match: |
| 155 | + file_path = os.path.abspath(os.path.join(PELOTON_DIR, |
| 156 | + file_match.group(1))) |
| 157 | + |
| 158 | + hunks_current_list = [] |
| 159 | + if file_path.endswith(".h") or file_path.endswith(".cpp"): |
| 160 | + target_files.append((file_path, hunks_current_list)) |
| 161 | + # If this file is not .cpp/.h the hunks_current_list reference |
| 162 | + # will point to an empty list which will be discarded later |
| 163 | + elif hunk_match: |
| 164 | + # add entry in the hunk list of the last file |
| 165 | + if hunk_match.group(4) is None: |
| 166 | + hunk = (int(hunk_match.group(2)), int(hunk_match.group(2))) |
| 167 | + else: |
| 168 | + hunk = (int(hunk_match.group(2)), int(hunk_match.group(2)) + |
| 169 | + int(hunk_match.group(4))) |
| 170 | + hunks_current_list.append(hunk) |
| 171 | + |
| 172 | + return target_files |
0 commit comments