Skip to content

Commit 986bc3d

Browse files
authored
[clang-format] Fix a serious bug in git clang-format -f (#102629)
With the --force (or -f) option, git-clang-format wipes out input files excluded by a .clang-format-ignore file if they have unstaged changes. This patch adds a hidden clang-format option --list-ignored that lists such excluded files for git-clang-format to filter out. Fixes #102459.
1 parent c5a4291 commit 986bc3d

File tree

3 files changed

+84
-3
lines changed

3 files changed

+84
-3
lines changed

clang/test/Format/list-ignored.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: rm -rf %t.dir
2+
// RUN: mkdir -p %t.dir/level1/level2
3+
4+
// RUN: cd %t.dir
5+
// RUN: echo "*" > .clang-format-ignore
6+
// RUN: echo "level*/*.c*" >> .clang-format-ignore
7+
// RUN: echo "*/*2/foo.*" >> .clang-format-ignore
8+
9+
// RUN: touch foo.cc
10+
// RUN: clang-format -list-ignored .clang-format-ignore foo.cc \
11+
// RUN: | FileCheck %s
12+
// CHECK: .clang-format-ignore
13+
// CHECK-NEXT: foo.cc
14+
15+
// RUN: cd level1
16+
// RUN: touch bar.cc baz.c
17+
// RUN: clang-format -list-ignored bar.cc baz.c \
18+
// RUN: | FileCheck %s -check-prefix=CHECK2
19+
// CHECK2: bar.cc
20+
// CHECK2-NEXT: baz.c
21+
22+
// RUN: cd level2
23+
// RUN: touch foo.c foo.js
24+
// RUN: clang-format -list-ignored foo.c foo.js \
25+
// RUN: | FileCheck %s -check-prefix=CHECK3
26+
// CHECK3: foo.c
27+
// CHECK3-NEXT: foo.js
28+
29+
// RUN: touch .clang-format-ignore
30+
// RUN: clang-format -list-ignored foo.c foo.js \
31+
// RUN: | FileCheck %s -allow-empty -check-prefix=CHECK4
32+
// CHECK4-NOT: foo.c
33+
// CHECK4-NOT: foo.js
34+
35+
// RUN: echo "*.js" > .clang-format-ignore
36+
// RUN: clang-format -list-ignored foo.c foo.js \
37+
// RUN: | FileCheck %s -check-prefix=CHECK5
38+
// CHECK5-NOT: foo.c
39+
// CHECK5: foo.js
40+
41+
// RUN: cd ../..
42+
// RUN: clang-format -list-ignored *.cc level1/*.c* level1/level2/foo.* \
43+
// RUN: | FileCheck %s -check-prefix=CHECK6
44+
// CHECK6: foo.cc
45+
// CHECK6-NEXT: bar.cc
46+
// CHECK6-NEXT: baz.c
47+
// CHECK6-NOT: foo.c
48+
// CHECK6-NEXT: foo.js
49+
50+
// RUN: rm .clang-format-ignore
51+
// RUN: clang-format -list-ignored *.cc level1/*.c* level1/level2/foo.* \
52+
// RUN: | FileCheck %s -check-prefix=CHECK7
53+
// CHECK7-NOT: foo.cc
54+
// CHECK7-NOT: bar.cc
55+
// CHECK7-NOT: baz.c
56+
// CHECK7-NOT: foo.c
57+
// CHECK7: foo.js
58+
59+
// RUN: cd ..
60+
// RUN: rm -r %t.dir

clang/tools/clang-format/ClangFormat.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ static cl::opt<bool> FailOnIncompleteFormat(
210210
cl::desc("If set, fail with exit code 1 on incomplete format."),
211211
cl::init(false), cl::cat(ClangFormatCategory));
212212

213+
static cl::opt<bool> ListIgnored("list-ignored",
214+
cl::desc("List ignored files."),
215+
cl::cat(ClangFormatCategory), cl::Hidden);
216+
213217
namespace clang {
214218
namespace format {
215219

@@ -715,7 +719,13 @@ int main(int argc, const char **argv) {
715719
unsigned FileNo = 1;
716720
bool Error = false;
717721
for (const auto &FileName : FileNames) {
718-
if (isIgnored(FileName))
722+
const bool Ignored = isIgnored(FileName);
723+
if (ListIgnored) {
724+
if (Ignored)
725+
outs() << FileName << '\n';
726+
continue;
727+
}
728+
if (Ignored)
719729
continue;
720730
if (Verbose) {
721731
errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "

clang/tools/clang-format/git-clang-format

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,12 @@ def main():
173173
# those files.
174174
cd_to_toplevel()
175175
filter_symlinks(changed_lines)
176+
filter_ignored_files(changed_lines, binary=opts.binary)
176177
if opts.verbose >= 1:
177178
ignored_files.difference_update(changed_lines)
178179
if ignored_files:
179-
print(
180-
'Ignoring changes in the following files (wrong extension or symlink):')
180+
print('Ignoring the following files (wrong extension, symlink, or '
181+
'ignored by clang-format):')
181182
for filename in ignored_files:
182183
print(' %s' % filename)
183184
if changed_lines:
@@ -399,6 +400,16 @@ def filter_symlinks(dictionary):
399400
del dictionary[filename]
400401

401402

403+
def filter_ignored_files(dictionary, binary):
404+
"""Delete every key in `dictionary` that is ignored by clang-format."""
405+
ignored_files = run(binary, '-list-ignored', *dictionary.keys())
406+
if not ignored_files:
407+
return
408+
ignored_files = ignored_files.split('\n')
409+
for filename in ignored_files:
410+
del dictionary[filename]
411+
412+
402413
def cd_to_toplevel():
403414
"""Change to the top level of the git repository."""
404415
toplevel = run('git', 'rev-parse', '--show-toplevel')

0 commit comments

Comments
 (0)