Author: Owen Pan Date: 2024-08-10T13:31:35-07:00 New Revision: 986bc3d0719af653fecb77e8cfc59f39bec148fd
URL: https://github.com/llvm/llvm-project/commit/986bc3d0719af653fecb77e8cfc59f39bec148fd DIFF: https://github.com/llvm/llvm-project/commit/986bc3d0719af653fecb77e8cfc59f39bec148fd.diff LOG: [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. Added: clang/test/Format/list-ignored.cpp Modified: clang/tools/clang-format/ClangFormat.cpp clang/tools/clang-format/git-clang-format Removed: ################################################################################ diff --git a/clang/test/Format/list-ignored.cpp b/clang/test/Format/list-ignored.cpp new file mode 100644 index 00000000000000..6e65a68a6f9968 --- /dev/null +++ b/clang/test/Format/list-ignored.cpp @@ -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 diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp index 6582d73eae40ea..54b1dacbbe854a 100644 --- a/clang/tools/clang-format/ClangFormat.cpp +++ b/clang/tools/clang-format/ClangFormat.cpp @@ -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() << "] " diff --git a/clang/tools/clang-format/git-clang-format b/clang/tools/clang-format/git-clang-format index d33fd478d77fd9..714ba8a6e77d51 100755 --- a/clang/tools/clang-format/git-clang-format +++ b/clang/tools/clang-format/git-clang-format @@ -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. diff erence_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') _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits