3 # Script to clang-format suricata C code changes
5 # Rewriting branch parts of it is inspired by
6 # https://www.thetopsites.net/article/53885283.shtml
10 # We verify the minimal clang-format version for better error messaging as older clang-format
11 # will barf on unknown settings with a generic error.
12 CLANG_FORMAT_REQUIRED_VERSION
=9
15 EXIT_CODE_FORMATTING_REQUIRED
=1
19 # Debug output if PRINT_DEBUG is 1
21 if [ $PRINT_DEBUG -ne 0 ]; then
26 # ignore text formatting by default
30 # $TERM is set to dumb when calling scripts in github actions.
31 if [ -n "$TERM" -a "$TERM" != "dumb" ]; then
33 # tput, albeit unlikely, might not be installed
34 command -v tput >/dev
/null
2>&1 # built-in which
36 Debug
"Setting text formatting"
39 italic
=$
(echo -e '\E[3m')
42 Debug
"No text formatting"
46 pushd .
>/dev
/null
# we might change dir - save so that we can revert
51 $EXEC <command> [<args>]
53 Format selected changes using clang-format.
55 Note: This does ONLY format the changed code, not the whole file! It
56 uses ${italic}git-clang-format${normal} for the actual formatting. If you want to format
57 whole files, use ${italic}clang-format -i <file>${normal}.
59 It auto-detects the correct clang-format version and compared to ${italic}git-clang-format${normal}
60 proper it provides additional functionality such as reformatting of all commits on a branch.
62 Commands used in various situations:
64 Formatting branch changes (compared to master):
65 branch Format all changes in branch as additional commit
66 rewrite-branch Format every commit in branch and rewrite history
68 Formatting single changes:
69 cached Format changes in git staging
70 commit Format changes in most recent commit
72 Checking if formatting is correct:
73 check-branch Checks if formatting for branch changes is correct
75 More info an a command:
76 help Display more info for a particular <command>
80 HELP_BRANCH
=$
(cat << EOM
82 $EXEC branch - Format all changes in branch as additional commit
84 ${bold}SYNOPSIS${normal}
85 $EXEC branch [--force]
87 ${bold}DESCRIPTION${normal}
88 Format all changes in your branch enabling you to add it as an additional
89 formatting commit. It automatically detects all commits on your branch.
91 Requires that all changes are committed unless --force is provided.
93 You will need to commit the reformatted code.
95 This is equivalent to calling:
96 $ git clang-format --extensions c,h [--force] first_commit_on_current_branch^
98 ${bold}OPTIONS${normal}
100 Allow changes to unstaged files.
102 ${bold}EXAMPLES${normal}
103 On your branch whose changes you want to reformat:
107 ${bold}EXIT STATUS${normal}
108 $EXEC exits with a status of zero if the changes were successfully
109 formatted, or if no formatting change was required. A status of two will
110 be returned if any errors were encountered.
114 HELP_CACHED
=$
(cat << EOM
116 $EXEC cached - Format changes in git staging
118 ${bold}SYNOPSIS${normal}
119 $EXEC cached [--force]
121 ${bold}DESCRIPTION${normal}
122 Format staged changes using clang-format.
124 You will need to commit the reformatted code.
126 This is equivalent to calling:
127 $ git clang-format --extensions c,h [--force]
129 ${bold}OPTIONS${normal}
131 Allow changes to unstaged files.
133 ${bold}EXAMPLES${normal}
134 Format all changes in staging, i.e. in files added with ${italic}git add <file>${normal}.
138 ${bold}EXIT STATUS${normal}
139 $EXEC exits with a status of zero if the changes were successfully
140 formatted, or if no formatting change was required. A status of two will
141 be returned if any errors were encountered.
145 HELP_CHECK_BRANCH
=$
(cat << EOM
147 $EXEC check-branch - Checks if formatting for branch changes is correct
149 ${bold}SYNOPSIS${normal}
150 $EXEC check-branch [--show-commits] [--quiet]
151 $EXEC check-branch --diff [--show-commits] [--quiet]
152 $EXEC check-branch --diffstat [--show-commits] [--quiet]
154 ${bold}DESCRIPTION${normal}
155 Check if all branch changes are correctly formatted.
157 Note, it does not check every commit's formatting, but rather the
158 overall diff between HEAD and master.
160 Returns 1 if formatting is off, 0 if it is correct.
162 ${bold}OPTIONS${normal}
164 Print formatting diff, i.e. diff of each file with correct formatting.
166 Print formatting diffstat output, i.e. files with wrong formatting.
168 Print branch commits.
170 Do not print any error if formatting is off, only set exit code.
172 ${bold}EXIT STATUS${normal}
173 $EXEC exits with a status of zero if the formatting is correct. A
174 status of one will be returned if the formatting is not correct. A status
175 of two will be returned if any errors were encountered.
179 HELP_COMMIT
=$
(cat << EOM
181 $EXEC commit - Format changes in most recent commit
183 ${bold}SYNOPSIS${normal}
186 ${bold}DESCRIPTION${normal}
187 Format changes in most recent commit using clang-format.
189 You will need to commit the reformatted code.
191 This is equivalent to calling:
192 $ git clang-format --extensions c,h HEAD^
194 ${bold}EXAMPLES${normal}
195 Format all changes in most recent commit:
199 Note that this modifies the files, but doesn’t commit them – you’ll likely want to run
201 $ git commit --amend -a
203 ${bold}EXIT STATUS${normal}
204 $EXEC exits with a status of zero if the changes were successfully
205 formatted, or if no formatting change was required. A status of two will
206 be returned if any errors were encountered.
210 HELP_REWRITE_BRANCH
=$
(cat << EOM
212 $EXEC rewrite-branch - Format every commit in branch and rewrite history
214 ${bold}SYNOPSIS${normal}
217 ${bold}DESCRIPTION${normal}
218 Reformat all commits in branch off master one-by-one. This will ${bold}rewrite
219 the branch history${normal} using the existing commit metadata!
220 It automatically detects all commits on your branch.
222 This is handy in case you want to format all of your branch commits
223 while keeping the commits.
225 This can also be helpful if you have multiple commits in your branch and
226 the changed files have been reformatted, i.e. where a git rebase would
227 fail in many ways over-and-over again.
229 You can achieve the same manually on a separate branch by:
230 ${italic}git checkout -n <original_commit>${normal},
231 ${italic}git clang-format${normal} and ${italic}git commit${normal} for each original commit in your branch.
233 ${bold}OPTIONS${normal}
236 ${bold}EXAMPLES${normal}
237 In your branch that you want to reformat. Commit all your changes prior
240 $ $EXEC rewrite-branch
242 ${bold}EXIT STATUS${normal}
243 $EXEC exits with a status of zero if the changes were successfully
244 formatted, or if no formatting change was required. A status of two will
245 be returned if any errors were encountered.
249 # Error message on stderr
251 echo "${bold}ERROR${normal}: $@" 1>&2
254 # Exit program (and reset path)
256 popd >/dev
/null
# we might have changed dir
258 if [ $# -ne 1 ]; then
259 # Huh? No exit value provided?
260 Error
"Internal: ExitWith requires parameter"
261 exit $EXIT_CODE_ERROR
267 # Failure exit with error message
270 ExitWith
$EXIT_CODE_ERROR
273 # Ensure required program exists. Exits with failure if not found.
275 # RequireProgram ENVVAR_TO_SET program ...
276 # One can provide multiple alternative programs. Returns first program found in
278 function RequireProgram
{
279 if [ $# -lt 2 ]; then
280 Die
"Internal - RequireProgram: Need env and program parameters"
283 # eat variable to set
287 for program
in $@
; do
288 command -v $program >/dev
/null
2>&1 # built-in which
289 if [ $?
-eq 0 ]; then
290 eval "$envvar=$(command -v $program)"
295 if [ $# -eq 1 ]; then
298 Die
"None of $@ found"
302 # Make sure we are running from the top-level git directory.
303 # Same approach as for setup-decoder.sh. Good enough.
304 # We could probably use git rev-parse --show-toplevel to do so, as long as we
305 # handle the libhtp subfolder correctly.
306 function SetTopLevelDir
{
307 if [ -e .
/src
/suricata.c
]; then
310 elif [ -e .
/suricata.c
-o -e ..
/src
/suricata.c
]; then
313 Die
"This does not appear to be a suricata source directory."
317 # print help for given command
318 function HelpCommand
{
319 local help_command
=$1
320 local HELP_COMMAND
=$
(echo "HELP_$help_command" |
sed "s/-/_/g" |
tr [:lower
:] [:upper
:])
321 case $help_command in
322 branch|cached|check-branch|commit|rewrite-branch
)
323 echo "${!HELP_COMMAND}";
333 Die
"No manual entry for $help_command"
338 # Return first commit of branch (off master).
340 # Use $first_commit^ if you need the commit on master we branched off.
341 # Do not compare with master directly as it will diff with the latest commit
342 # on master. If our branch has not been rebased on the latest master, this
343 # would result in including all new commits on master!
344 function FirstCommitOfBranch
{
345 local first_commit
=$
(git rev-list origin
/master..HEAD |
tail -n 1)
349 # Check if branch formatting is correct.
350 # Compares with master branch as baseline which means it's limited to branches
352 # Exits with 1 if not, 0 if ok.
353 function CheckBranch
{
357 local show_diffstat
=0
359 local git_clang_format
="$GIT_CLANG_FORMAT --diff"
375 git_clang_format
="$GIT_CLANG_FORMAT_DIFFSTAT --diffstat"
385 echo "$HELP_CHECK_BRANCH";
387 Die
"Unknown $command option: $1"
392 if [ $show_diffstat -eq 1 -a $show_diff -eq 1 ]; then
393 echo "$HELP_CHECK_BRANCH";
395 Die
"Cannot combine $command options --diffstat with --diff"
398 # Find first commit on branch. Use $first_commit^ if you need the
399 # commit on master we branched off.
400 local first_commit
=$
(FirstCommitOfBranch
)
402 # git-clang-format is a python script that does not like SIGPIPE shut down
403 # by "| head" prematurely. Use work-around with writing to tmpfile first.
404 local format_changes
="$git_clang_format --extensions c,h $first_commit^"
405 local tmpfile
=$
(mktemp
/tmp
/clang-format.check.XXXXXX
)
406 $format_changes > $tmpfile
407 local changes
=$
(cat $tmpfile |
head -1)
408 if [ $show_diff -eq 1 -o $show_diffstat -eq 1 ]; then
414 # Branch commits can help with trouble shooting. Print after diff/diffstat
415 # as output might be tail'd
416 if [ $show_commits -eq 1 ]; then
417 echo "Commits on branch (new -> old):"
418 git log
--oneline $first_commit^..HEAD
421 if [ $quiet -ne 1 ]; then
422 echo "First commit on branch: $first_commit"
426 # Exit code of git-clang-format is useless as it's 0 no matter if files
427 # changed or not. Check actual output. Not ideal, but works.
428 if [ "${changes}" != "no modified files to format" -a \
429 "${changes}" != "clang-format did not modify any files" ]; then
430 if [ $quiet -ne 1 ]; then
431 Error
"Branch requires formatting"
432 Debug
"View required changes with clang-format: ${italic}$format_changes${normal}"
433 Error
"View required changes with: ${italic}$EXEC $command --diff${normal}"
434 Error
"Use ${italic}$EXEC rewrite-branch${normal} or ${italic}$EXEC branch${normal} to fix formatting"
435 ExitWith
$EXIT_CODE_FORMATTING_REQUIRED
437 return $EXIT_CODE_FORMATTING_REQUIRED
440 if [ $quiet -ne 1 ]; then
441 echo "no modified files to format"
447 # Reformat all changes in branch as a separate commit.
448 function ReformatBranch
{
451 if [ $# -gt 1 ]; then
454 Die
"Too many $command options: $1"
455 elif [ $# -eq 1 ]; then
456 if [ "$1" == "--force" -o "$1" == "-f" ]; then
457 with_unstaged
='--force'
461 Die
"Unknown $command option: $1"
465 # Find first commit on branch. Use $first_commit^ if you need the
466 # commit on master we branched off.
467 local first_commit
=$
(FirstCommitOfBranch
)
468 echo "First commit on branch: $first_commit"
470 $GIT_CLANG_FORMAT --style file --extensions c
,h
$with_unstaged $first_commit^
471 if [ $?
-ne 0 ]; then
472 Die
"Cannot reformat branch. git clang-format failed"
476 # Reformat changes in commit
477 function ReformatCommit
{
479 local commit
=HEAD^
# only most recent for now
480 if [ $# -gt 0 ]; then
481 echo "$HELP_MOST_RECENT";
483 Die
"Too many $command options: $1"
486 $GIT_CLANG_FORMAT --style file --extensions c
,h
$commit
487 if [ $?
-ne 0 ]; then
488 Die
"Cannot reformat most recent commit. git clang-format failed"
492 # Reformat currently staged changes
493 function ReformatCached
{
496 if [ $# -gt 1 ]; then
499 Die
"Too many $command options: $1"
500 elif [ $# -eq 1 ]; then
501 if [ "$1" == "--force" -o "$1" == "-f" ]; then
502 with_unstaged
='--force'
506 Die
"Unknown $command option: $1"
510 $GIT_CLANG_FORMAT --style file --extensions c
,h
$with_unstaged
511 if [ $?
-ne 0 ]; then
512 Die
"Cannot reformat staging. git clang-format failed"
516 # Reformat all commits of a branch (compared with master) and rewrites
517 # the history with the formatted commits one-by-one.
518 # This is helpful for quickly reformatting branches with multiple commits,
519 # or where the master version of a file has been reformatted.
521 # You can achieve the same manually by git checkout -n <commit>, git clang-format
522 # for each commit in your branch.
523 function ReformatCommitsOnBranch
{
524 # Do not allow rewriting of master.
525 # CheckBranch below will also tell us there are no changes compared with
526 # master, but let's make this foolproof and explicit here.
527 local current_branch
=$
(git rev-parse
--abbrev-ref HEAD
)
528 if [ "$current_branch" == "master" ]; then
529 Die
"Must not rewrite master branch history."
532 CheckBranch
"--quiet"
533 if [ $?
-eq 0 ]; then
534 echo "no modified files to format"
536 # Only rewrite if there are changes
537 # Squelch warning. Our usage of git filter-branch is limited and should be ok.
538 # Should investigate using git-filter-repo in the future instead.
539 export FILTER_BRANCH_SQUELCH_WARNING
=1
541 # Find first commit on branch. Use $first_commit^ if you need the
542 # commit on master we branched off.
543 local first_commit
=$
(FirstCommitOfBranch
)
544 echo "First commit on branch: $first_commit"
545 # Use --force in case it's run a second time on the same branch
546 git filter-branch
--force --tree-filter "$GIT_CLANG_FORMAT $first_commit^" -- $first_commit..HEAD
547 if [ $?
-ne 0 ]; then
548 Die
"Cannot rewrite branch. git filter-branch failed"
553 if [ $# -eq 0 ]; then
555 Die
"Missing arguments. Call with one argument"
560 RequireProgram GIT git
561 # ubuntu uses clang-format-{version} name for newer versions. fedora not.
562 RequireProgram GIT_CLANG_FORMAT git-clang-format-11 git-clang-format-10 git-clang-format-9 git-clang-format
563 GIT_CLANG_FORMAT_BINARY
=clang-format
564 if [[ $GIT_CLANG_FORMAT =~ .
*git-clang-format-11$
]]; then
565 # default binary is clang-format, specify the correct version.
566 # Alternative: git config clangformat.binary "clang-format-11"
567 GIT_CLANG_FORMAT_BINARY
="clang-format-11"
568 elif [[ $GIT_CLANG_FORMAT =~ .
*git-clang-format-10$
]]; then
569 # default binary is clang-format, specify the correct version.
570 # Alternative: git config clangformat.binary "clang-format-10"
571 GIT_CLANG_FORMAT_BINARY
="clang-format-10"
572 elif [[ $GIT_CLANG_FORMAT =~ .
*git-clang-format-9$
]]; then
573 # default binary is clang-format, specify the correct version.
574 # Alternative: git config clangformat.binary "clang-format-9"
575 GIT_CLANG_FORMAT_BINARY
="clang-format-9"
576 elif [[ $GIT_CLANG_FORMAT =~ .
*git-clang-format$
]]; then
577 Debug
"Using regular clang-format"
579 Debug
"Internal: unhandled clang-format version"
582 # enforce minimal clang-format version as required by .clang-format
583 clang_format_version
=$
($GIT_CLANG_FORMAT_BINARY --version |
sed 's/.*clang-format version \([0-9]*\.[0-9]*\.[0-9]*\).*/\1/')
584 Debug
"Found clang-format version: $clang_format_version"
585 clang_format_version_major
=$
(echo $clang_format_version |
sed 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\).*/\1/')
586 Debug
"clang-format version major: $clang_format_version_major"
587 if [ $
((clang_format_version_major
+ 0)) -lt $
((CLANG_FORMAT_REQUIRED_VERSION
+ 0)) ]; then
588 Die
"Require clang version $CLANG_FORMAT_REQUIRED_VERSION, found $clang_format_version_major ($clang_format_version)."
591 # overwite git-clang-version for --diffstat as upstream does not have that yet
592 RequireProgram GIT_CLANG_FORMAT_DIFFSTAT
scripts
/git-clang-format-custom
593 if [ "$GIT_CLANG_FORMAT_BINARY" != "clang-format" ]; then
594 GIT_CLANG_FORMAT
="$GIT_CLANG_FORMAT --binary $GIT_CLANG_FORMAT_BINARY"
595 GIT_CLANG_FORMAT_DIFFSTAT
="$GIT_CLANG_FORMAT_DIFFSTAT --binary $GIT_CLANG_FORMAT_BINARY"
597 Debug
"Using $GIT_CLANG_FORMAT"
598 Debug
"Using $GIT_CLANG_FORMAT_DIFFSTAT"
625 ReformatCommitsOnBranch
638 Die
"$EXEC: '$command' is not a command. See '$EXEC --help'"