[clang] [clang-format] Fix a serious bug in `git clang-format -f` (PR #102629)
Owen Pan via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 9 09:21:40 PDT 2024
https://github.com/owenca updated https://github.com/llvm/llvm-project/pull/102629
>From 9b71b289feb75cdc6d67a6ac696ff0ba7969549d Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Fri, 9 Aug 2024 08:15:43 -0700
Subject: [PATCH 1/3] [clang-format] Fix a serious bug in `git clang-format -f`
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.
---
clang/test/Format/list-ignored.cpp | 60 +++++++++++++++++++++++
clang/tools/clang-format/ClangFormat.cpp | 12 ++++-
clang/tools/clang-format/git-clang-format | 12 ++++-
3 files changed, 81 insertions(+), 3 deletions(-)
create mode 100644 clang/test/Format/list-ignored.cpp
diff --git a/clang/test/Format/list-ignored.cpp b/clang/test/Format/list-ignored.cpp
new file mode 100644
index 00000000000000..91f2b5175d6395
--- /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 -match-full-lines
+// 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 -match-full-lines
+// 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 -match-full-lines
+// 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 -match-full-lines
+// 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 -match-full-lines
+// CHECK6-NOT: level1/levle2/foo.c
+// CHECK6: foo.cc
+// CHECK6-NEXT: level1/bar.cc
+// CHECK6-NEXT: level1/baz.c
+// CHECK6-NEXT: level1/level2/foo.js
+
+// RUN: rm .clang-format-ignore
+// RUN: clang-format -list-ignored *.cc level1/*.c* level1/level2/foo.* \
+// RUN: | FileCheck %s -check-prefix=CHECK7 -match-full-lines
+// CHECK7-NOT: foo.cc
+// CHECK7-NOT: level1/bar.cc
+// CHECK7-NOT: level1/baz.c
+// CHECK7-NOT: level1/level2/foo.c
+// CHECK7: level1/level2/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..50dd5345a6db33 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..8f7eb38d72e738 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.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,13 @@ 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()).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')
>From d081b62432ad00130481272044fc28440e6b08fe Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Fri, 9 Aug 2024 08:30:13 -0700
Subject: [PATCH 2/3] Update ClangFormat.cpp
Changes the `"\n"` to `'\n'`.
---
clang/tools/clang-format/ClangFormat.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index 50dd5345a6db33..54b1dacbbe854a 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -722,7 +722,7 @@ int main(int argc, const char **argv) {
const bool Ignored = isIgnored(FileName);
if (ListIgnored) {
if (Ignored)
- outs() << FileName << "\n";
+ outs() << FileName << '\n';
continue;
}
if (Ignored)
>From 7a0ac9f6d4e6906284b585701e2896be626cb27e Mon Sep 17 00:00:00 2001
From: Owen Pan <owenpiano at gmail.com>
Date: Fri, 9 Aug 2024 09:21:31 -0700
Subject: [PATCH 3/3] Update git-clang-format
Handles the case when `clang-format -list-ignored` outputs nothing.
---
clang/tools/clang-format/git-clang-format | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/clang/tools/clang-format/git-clang-format b/clang/tools/clang-format/git-clang-format
index 8f7eb38d72e738..714ba8a6e77d51 100755
--- a/clang/tools/clang-format/git-clang-format
+++ b/clang/tools/clang-format/git-clang-format
@@ -402,7 +402,10 @@ def filter_symlinks(dictionary):
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()).split('\n')
+ 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]
More information about the cfe-commits
mailing list