[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.
This commit is contained in:
parent
c5a4291fb7
commit
986bc3d071
60
clang/test/Format/list-ignored.cpp
Normal file
60
clang/test/Format/list-ignored.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
// RUN: rm -rf %t.dir
|
||||
// RUN: mkdir -p %t.dir/level1/level2
|
||||
|
||||
// RUN: cd %t.dir
|
||||
// RUN: echo "*" > .clang-format-ignore
|
||||
// RUN: echo "level*/*.c*" >> .clang-format-ignore
|
||||
// RUN: echo "*/*2/foo.*" >> .clang-format-ignore
|
||||
|
||||
// RUN: touch foo.cc
|
||||
// RUN: clang-format -list-ignored .clang-format-ignore foo.cc \
|
||||
// RUN: | FileCheck %s
|
||||
// CHECK: .clang-format-ignore
|
||||
// CHECK-NEXT: foo.cc
|
||||
|
||||
// RUN: cd level1
|
||||
// RUN: touch bar.cc baz.c
|
||||
// RUN: clang-format -list-ignored bar.cc baz.c \
|
||||
// RUN: | FileCheck %s -check-prefix=CHECK2
|
||||
// CHECK2: bar.cc
|
||||
// CHECK2-NEXT: baz.c
|
||||
|
||||
// RUN: cd level2
|
||||
// RUN: touch foo.c foo.js
|
||||
// RUN: clang-format -list-ignored foo.c foo.js \
|
||||
// RUN: | FileCheck %s -check-prefix=CHECK3
|
||||
// CHECK3: foo.c
|
||||
// CHECK3-NEXT: foo.js
|
||||
|
||||
// RUN: touch .clang-format-ignore
|
||||
// RUN: clang-format -list-ignored foo.c foo.js \
|
||||
// RUN: | FileCheck %s -allow-empty -check-prefix=CHECK4
|
||||
// CHECK4-NOT: foo.c
|
||||
// CHECK4-NOT: foo.js
|
||||
|
||||
// RUN: echo "*.js" > .clang-format-ignore
|
||||
// RUN: clang-format -list-ignored foo.c foo.js \
|
||||
// RUN: | FileCheck %s -check-prefix=CHECK5
|
||||
// CHECK5-NOT: foo.c
|
||||
// CHECK5: foo.js
|
||||
|
||||
// RUN: cd ../..
|
||||
// RUN: clang-format -list-ignored *.cc level1/*.c* level1/level2/foo.* \
|
||||
// RUN: | FileCheck %s -check-prefix=CHECK6
|
||||
// CHECK6: foo.cc
|
||||
// CHECK6-NEXT: bar.cc
|
||||
// CHECK6-NEXT: baz.c
|
||||
// CHECK6-NOT: foo.c
|
||||
// CHECK6-NEXT: foo.js
|
||||
|
||||
// RUN: rm .clang-format-ignore
|
||||
// RUN: clang-format -list-ignored *.cc level1/*.c* level1/level2/foo.* \
|
||||
// RUN: | FileCheck %s -check-prefix=CHECK7
|
||||
// CHECK7-NOT: foo.cc
|
||||
// CHECK7-NOT: bar.cc
|
||||
// CHECK7-NOT: baz.c
|
||||
// CHECK7-NOT: foo.c
|
||||
// CHECK7: foo.js
|
||||
|
||||
// RUN: cd ..
|
||||
// RUN: rm -r %t.dir
|
||||
@ -210,6 +210,10 @@ static cl::opt<bool> FailOnIncompleteFormat(
|
||||
cl::desc("If set, fail with exit code 1 on incomplete format."),
|
||||
cl::init(false), cl::cat(ClangFormatCategory));
|
||||
|
||||
static cl::opt<bool> ListIgnored("list-ignored",
|
||||
cl::desc("List ignored files."),
|
||||
cl::cat(ClangFormatCategory), cl::Hidden);
|
||||
|
||||
namespace clang {
|
||||
namespace format {
|
||||
|
||||
@ -715,7 +719,13 @@ int main(int argc, const char **argv) {
|
||||
unsigned FileNo = 1;
|
||||
bool Error = false;
|
||||
for (const auto &FileName : FileNames) {
|
||||
if (isIgnored(FileName))
|
||||
const bool Ignored = isIgnored(FileName);
|
||||
if (ListIgnored) {
|
||||
if (Ignored)
|
||||
outs() << FileName << '\n';
|
||||
continue;
|
||||
}
|
||||
if (Ignored)
|
||||
continue;
|
||||
if (Verbose) {
|
||||
errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
|
||||
|
||||
@ -173,11 +173,12 @@ def main():
|
||||
# those files.
|
||||
cd_to_toplevel()
|
||||
filter_symlinks(changed_lines)
|
||||
filter_ignored_files(changed_lines, binary=opts.binary)
|
||||
if opts.verbose >= 1:
|
||||
ignored_files.difference_update(changed_lines)
|
||||
if ignored_files:
|
||||
print(
|
||||
'Ignoring changes in the following files (wrong extension or symlink):')
|
||||
print('Ignoring the following files (wrong extension, symlink, or '
|
||||
'ignored by clang-format):')
|
||||
for filename in ignored_files:
|
||||
print(' %s' % filename)
|
||||
if changed_lines:
|
||||
@ -399,6 +400,16 @@ def filter_symlinks(dictionary):
|
||||
del dictionary[filename]
|
||||
|
||||
|
||||
def filter_ignored_files(dictionary, binary):
|
||||
"""Delete every key in `dictionary` that is ignored by clang-format."""
|
||||
ignored_files = run(binary, '-list-ignored', *dictionary.keys())
|
||||
if not ignored_files:
|
||||
return
|
||||
ignored_files = ignored_files.split('\n')
|
||||
for filename in ignored_files:
|
||||
del dictionary[filename]
|
||||
|
||||
|
||||
def cd_to_toplevel():
|
||||
"""Change to the top level of the git repository."""
|
||||
toplevel = run('git', 'rev-parse', '--show-toplevel')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user