clang-format.sh 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. #!/usr/bin/env bash
  2. #
  3. # Script to clang-format suricata C code changes
  4. #
  5. # Rewriting branch parts of it is inspired by
  6. # https://www.thetopsites.net/article/53885283.shtml
  7. #set -x
  8. # We verify the minimal clang-format version for better error messaging as older clang-format
  9. # will barf on unknown settings with a generic error.
  10. CLANG_FORMAT_REQUIRED_VERSION=9
  11. EXIT_CODE_ERROR=2
  12. EXIT_CODE_FORMATTING_REQUIRED=1
  13. EXIT_CODE_OK=0
  14. PRINT_DEBUG=0
  15. # Debug output if PRINT_DEBUG is 1
  16. function Debug {
  17. if [ $PRINT_DEBUG -ne 0 ]; then
  18. echo "DEBUG: $@"
  19. fi
  20. }
  21. # ignore text formatting by default
  22. bold=
  23. normal=
  24. italic=
  25. # $TERM is set to dumb when calling scripts in github actions.
  26. if [ -n "$TERM" -a "$TERM" != "dumb" ]; then
  27. Debug "TERM: '$TERM'"
  28. # tput, albeit unlikely, might not be installed
  29. command -v tput >/dev/null 2>&1 # built-in which
  30. if [ $? -eq 0 ]; then
  31. Debug "Setting text formatting"
  32. bold=$(tput bold)
  33. normal=$(tput sgr0)
  34. italic=$(echo -e '\E[3m')
  35. fi
  36. else
  37. Debug "No text formatting"
  38. fi
  39. EXEC=$(basename $0)
  40. pushd . >/dev/null # we might change dir - save so that we can revert
  41. USAGE=$(cat << EOM
  42. usage: $EXEC --help
  43. $EXEC help <command>
  44. $EXEC <command> [<args>]
  45. Format selected changes using clang-format.
  46. Note: This does ONLY format the changed code, not the whole file! It
  47. uses ${italic}git-clang-format${normal} for the actual formatting. If you want to format
  48. whole files, use ${italic}clang-format -i <file>${normal}.
  49. It auto-detects the correct clang-format version and compared to ${italic}git-clang-format${normal}
  50. proper it provides additional functionality such as reformatting of all commits on a branch.
  51. Commands used in various situations:
  52. Formatting branch changes (compared to master):
  53. branch Format all changes in branch as additional commit
  54. rewrite-branch Format every commit in branch and rewrite history
  55. Formatting single changes:
  56. cached Format changes in git staging
  57. commit Format changes in most recent commit
  58. Checking if formatting is correct:
  59. check-branch Checks if formatting for branch changes is correct
  60. More info an a command:
  61. help Display more info for a particular <command>
  62. EOM
  63. )
  64. HELP_BRANCH=$(cat << EOM
  65. ${bold}NAME${normal}
  66. $EXEC branch - Format all changes in branch as additional commit
  67. ${bold}SYNOPSIS${normal}
  68. $EXEC branch [--force]
  69. ${bold}DESCRIPTION${normal}
  70. Format all changes in your branch enabling you to add it as an additional
  71. formatting commit. It automatically detects all commits on your branch.
  72. Requires that all changes are committed unless --force is provided.
  73. You will need to commit the reformatted code.
  74. This is equivalent to calling:
  75. $ git clang-format --extensions c,h [--force] first_commit_on_current_branch^
  76. ${bold}OPTIONS${normal}
  77. -f, --force
  78. Allow changes to unstaged files.
  79. ${bold}EXAMPLES${normal}
  80. On your branch whose changes you want to reformat:
  81. $ $EXEC branch
  82. ${bold}EXIT STATUS${normal}
  83. $EXEC exits with a status of zero if the changes were successfully
  84. formatted, or if no formatting change was required. A status of two will
  85. be returned if any errors were encountered.
  86. EOM
  87. )
  88. HELP_CACHED=$(cat << EOM
  89. ${bold}NAME${normal}
  90. $EXEC cached - Format changes in git staging
  91. ${bold}SYNOPSIS${normal}
  92. $EXEC cached [--force]
  93. ${bold}DESCRIPTION${normal}
  94. Format staged changes using clang-format.
  95. You will need to commit the reformatted code.
  96. This is equivalent to calling:
  97. $ git clang-format --extensions c,h [--force]
  98. ${bold}OPTIONS${normal}
  99. -f, --force
  100. Allow changes to unstaged files.
  101. ${bold}EXAMPLES${normal}
  102. Format all changes in staging, i.e. in files added with ${italic}git add <file>${normal}.
  103. $ $EXEC cached
  104. ${bold}EXIT STATUS${normal}
  105. $EXEC exits with a status of zero if the changes were successfully
  106. formatted, or if no formatting change was required. A status of two will
  107. be returned if any errors were encountered.
  108. EOM
  109. )
  110. HELP_CHECK_BRANCH=$(cat << EOM
  111. ${bold}NAME${normal}
  112. $EXEC check-branch - Checks if formatting for branch changes is correct
  113. ${bold}SYNOPSIS${normal}
  114. $EXEC check-branch [--show-commits] [--quiet]
  115. $EXEC check-branch --diff [--show-commits] [--quiet]
  116. $EXEC check-branch --diffstat [--show-commits] [--quiet]
  117. ${bold}DESCRIPTION${normal}
  118. Check if all branch changes are correctly formatted.
  119. Note, it does not check every commit's formatting, but rather the
  120. overall diff between HEAD and master.
  121. Returns 1 if formatting is off, 0 if it is correct.
  122. ${bold}OPTIONS${normal}
  123. -d, --diff
  124. Print formatting diff, i.e. diff of each file with correct formatting.
  125. -s, --diffstat
  126. Print formatting diffstat output, i.e. files with wrong formatting.
  127. -c, --show-commits
  128. Print branch commits.
  129. -q, --quiet
  130. Do not print any error if formatting is off, only set exit code.
  131. ${bold}EXIT STATUS${normal}
  132. $EXEC exits with a status of zero if the formatting is correct. A
  133. status of one will be returned if the formatting is not correct. A status
  134. of two will be returned if any errors were encountered.
  135. EOM
  136. )
  137. HELP_COMMIT=$(cat << EOM
  138. ${bold}NAME${normal}
  139. $EXEC commit - Format changes in most recent commit
  140. ${bold}SYNOPSIS${normal}
  141. $EXEC commit
  142. ${bold}DESCRIPTION${normal}
  143. Format changes in most recent commit using clang-format.
  144. You will need to commit the reformatted code.
  145. This is equivalent to calling:
  146. $ git clang-format --extensions c,h HEAD^
  147. ${bold}EXAMPLES${normal}
  148. Format all changes in most recent commit:
  149. $ $EXEC commit
  150. Note that this modifies the files, but doesn’t commit them – you’ll likely want to run
  151. $ git commit --amend -a
  152. ${bold}EXIT STATUS${normal}
  153. $EXEC exits with a status of zero if the changes were successfully
  154. formatted, or if no formatting change was required. A status of two will
  155. be returned if any errors were encountered.
  156. EOM
  157. )
  158. HELP_REWRITE_BRANCH=$(cat << EOM
  159. ${bold}NAME${normal}
  160. $EXEC rewrite-branch - Format every commit in branch and rewrite history
  161. ${bold}SYNOPSIS${normal}
  162. $EXEC rewrite-branch
  163. ${bold}DESCRIPTION${normal}
  164. Reformat all commits in branch off master one-by-one. This will ${bold}rewrite
  165. the branch history${normal} using the existing commit metadata!
  166. It automatically detects all commits on your branch.
  167. This is handy in case you want to format all of your branch commits
  168. while keeping the commits.
  169. This can also be helpful if you have multiple commits in your branch and
  170. the changed files have been reformatted, i.e. where a git rebase would
  171. fail in many ways over-and-over again.
  172. You can achieve the same manually on a separate branch by:
  173. ${italic}git checkout -n <original_commit>${normal},
  174. ${italic}git clang-format${normal} and ${italic}git commit${normal} for each original commit in your branch.
  175. ${bold}OPTIONS${normal}
  176. None
  177. ${bold}EXAMPLES${normal}
  178. In your branch that you want to reformat. Commit all your changes prior
  179. to calling:
  180. $ $EXEC rewrite-branch
  181. ${bold}EXIT STATUS${normal}
  182. $EXEC exits with a status of zero if the changes were successfully
  183. formatted, or if no formatting change was required. A status of two will
  184. be returned if any errors were encountered.
  185. EOM
  186. )
  187. # Error message on stderr
  188. function Error {
  189. echo "${bold}ERROR${normal}: $@" 1>&2
  190. }
  191. # Exit program (and reset path)
  192. function ExitWith {
  193. popd >/dev/null # we might have changed dir
  194. if [ $# -ne 1 ]; then
  195. # Huh? No exit value provided?
  196. Error "Internal: ExitWith requires parameter"
  197. exit $EXIT_CODE_ERROR
  198. else
  199. exit $1
  200. fi
  201. }
  202. # Failure exit with error message
  203. function Die {
  204. Error $@
  205. ExitWith $EXIT_CODE_ERROR
  206. }
  207. # Ensure required program exists. Exits with failure if not found.
  208. # Call with
  209. # RequireProgram ENVVAR_TO_SET program ...
  210. # One can provide multiple alternative programs. Returns first program found in
  211. # provided list.
  212. function RequireProgram {
  213. if [ $# -lt 2 ]; then
  214. Die "Internal - RequireProgram: Need env and program parameters"
  215. fi
  216. # eat variable to set
  217. local envvar=$1
  218. shift
  219. for program in $@; do
  220. command -v $program >/dev/null 2>&1 # built-in which
  221. if [ $? -eq 0 ]; then
  222. eval "$envvar=$(command -v $program)"
  223. return
  224. fi
  225. done
  226. if [ $# -eq 1 ]; then
  227. Die "$1 not found"
  228. else
  229. Die "None of $@ found"
  230. fi
  231. }
  232. # Make sure we are running from the top-level git directory.
  233. # Same approach as for setup-decoder.sh. Good enough.
  234. # We could probably use git rev-parse --show-toplevel to do so, as long as we
  235. # handle the libhtp subfolder correctly.
  236. function SetTopLevelDir {
  237. if [ -e ./src/suricata.c ]; then
  238. # Do nothing.
  239. true
  240. elif [ -e ./suricata.c -o -e ../src/suricata.c ]; then
  241. cd ..
  242. else
  243. Die "This does not appear to be a suricata source directory."
  244. fi
  245. }
  246. # print help for given command
  247. function HelpCommand {
  248. local help_command=$1
  249. local HELP_COMMAND=$(echo "HELP_$help_command" | sed "s/-/_/g" | tr [:lower:] [:upper:])
  250. case $help_command in
  251. branch|cached|check-branch|commit|rewrite-branch)
  252. echo "${!HELP_COMMAND}";
  253. ;;
  254. "")
  255. echo "$USAGE";
  256. ;;
  257. *)
  258. echo "$USAGE";
  259. echo "";
  260. Die "No manual entry for $help_command"
  261. ;;
  262. esac
  263. }
  264. # Return first commit of branch (off master).
  265. #
  266. # Use $first_commit^ if you need the commit on master we branched off.
  267. # Do not compare with master directly as it will diff with the latest commit
  268. # on master. If our branch has not been rebased on the latest master, this
  269. # would result in including all new commits on master!
  270. function FirstCommitOfBranch {
  271. local first_commit=$(git rev-list origin/master..HEAD | tail -n 1)
  272. echo $first_commit
  273. }
  274. # Check if branch formatting is correct.
  275. # Compares with master branch as baseline which means it's limited to branches
  276. # other than master.
  277. # Exits with 1 if not, 0 if ok.
  278. function CheckBranch {
  279. # check parameters
  280. local quiet=0
  281. local show_diff=0
  282. local show_diffstat=0
  283. local show_commits=0
  284. local git_clang_format="$GIT_CLANG_FORMAT --diff"
  285. while [[ $# -gt 0 ]]
  286. do
  287. case "$1" in
  288. -q|--quiet)
  289. quiet=1
  290. shift
  291. ;;
  292. -d|--diff)
  293. show_diff=1
  294. shift
  295. ;;
  296. -s|--diffstat)
  297. show_diffstat=1
  298. git_clang_format="$GIT_CLANG_FORMAT_DIFFSTAT --diffstat"
  299. shift
  300. ;;
  301. -c|--show-commits)
  302. show_commits=1
  303. shift
  304. ;;
  305. *) # unknown option
  306. echo "$HELP_CHECK_BRANCH";
  307. echo "";
  308. Die "Unknown $command option: $1"
  309. ;;
  310. esac
  311. done
  312. if [ $show_diffstat -eq 1 -a $show_diff -eq 1 ]; then
  313. echo "$HELP_CHECK_BRANCH";
  314. echo "";
  315. Die "Cannot combine $command options --diffstat with --diff"
  316. fi
  317. # Find first commit on branch. Use $first_commit^ if you need the
  318. # commit on master we branched off.
  319. local first_commit=$(FirstCommitOfBranch)
  320. # git-clang-format is a python script that does not like SIGPIPE shut down
  321. # by "| head" prematurely. Use work-around with writing to tmpfile first.
  322. local format_changes="$git_clang_format --extensions c,h $first_commit^"
  323. local tmpfile=$(mktemp /tmp/clang-format.check.XXXXXX)
  324. $format_changes > $tmpfile
  325. local changes=$(cat $tmpfile | head -1)
  326. if [ $show_diff -eq 1 -o $show_diffstat -eq 1 ]; then
  327. cat $tmpfile
  328. echo ""
  329. fi
  330. rm $tmpfile
  331. # Branch commits can help with trouble shooting. Print after diff/diffstat
  332. # as output might be tail'd
  333. if [ $show_commits -eq 1 ]; then
  334. echo "Commits on branch (new -> old):"
  335. git log --oneline $first_commit^..HEAD
  336. echo ""
  337. else
  338. if [ $quiet -ne 1 ]; then
  339. echo "First commit on branch: $first_commit"
  340. fi
  341. fi
  342. # Exit code of git-clang-format is useless as it's 0 no matter if files
  343. # changed or not. Check actual output. Not ideal, but works.
  344. if [ "${changes}" != "no modified files to format" -a \
  345. "${changes}" != "clang-format did not modify any files" ]; then
  346. if [ $quiet -ne 1 ]; then
  347. Error "Branch requires formatting"
  348. Debug "View required changes with clang-format: ${italic}$format_changes${normal}"
  349. Error "View required changes with: ${italic}$EXEC $command --diff${normal}"
  350. Error "Use ${italic}$EXEC rewrite-branch${normal} or ${italic}$EXEC branch${normal} to fix formatting"
  351. ExitWith $EXIT_CODE_FORMATTING_REQUIRED
  352. else
  353. return $EXIT_CODE_FORMATTING_REQUIRED
  354. fi
  355. else
  356. if [ $quiet -ne 1 ]; then
  357. echo "no modified files to format"
  358. fi
  359. return $EXIT_CODE_OK
  360. fi
  361. }
  362. # Reformat all changes in branch as a separate commit.
  363. function ReformatBranch {
  364. # check parameters
  365. local with_unstaged=
  366. if [ $# -gt 1 ]; then
  367. echo "$HELP_BRANCH";
  368. echo "";
  369. Die "Too many $command options: $1"
  370. elif [ $# -eq 1 ]; then
  371. if [ "$1" == "--force" -o "$1" == "-f" ]; then
  372. with_unstaged='--force'
  373. else
  374. echo "$HELP_BRANCH";
  375. echo "";
  376. Die "Unknown $command option: $1"
  377. fi
  378. fi
  379. # Find first commit on branch. Use $first_commit^ if you need the
  380. # commit on master we branched off.
  381. local first_commit=$(FirstCommitOfBranch)
  382. echo "First commit on branch: $first_commit"
  383. $GIT_CLANG_FORMAT --style file --extensions c,h $with_unstaged $first_commit^
  384. if [ $? -ne 0 ]; then
  385. Die "Cannot reformat branch. git clang-format failed"
  386. fi
  387. }
  388. # Reformat changes in commit
  389. function ReformatCommit {
  390. # check parameters
  391. local commit=HEAD^ # only most recent for now
  392. if [ $# -gt 0 ]; then
  393. echo "$HELP_MOST_RECENT";
  394. echo "";
  395. Die "Too many $command options: $1"
  396. fi
  397. $GIT_CLANG_FORMAT --style file --extensions c,h $commit
  398. if [ $? -ne 0 ]; then
  399. Die "Cannot reformat most recent commit. git clang-format failed"
  400. fi
  401. }
  402. # Reformat currently staged changes
  403. function ReformatCached {
  404. # check parameters
  405. local with_unstaged=
  406. if [ $# -gt 1 ]; then
  407. echo "$HELP_CACHED";
  408. echo "";
  409. Die "Too many $command options: $1"
  410. elif [ $# -eq 1 ]; then
  411. if [ "$1" == "--force" -o "$1" == "-f" ]; then
  412. with_unstaged='--force'
  413. else
  414. echo "$HELP_CACHED";
  415. echo "";
  416. Die "Unknown $command option: $1"
  417. fi
  418. fi
  419. $GIT_CLANG_FORMAT --style file --extensions c,h $with_unstaged
  420. if [ $? -ne 0 ]; then
  421. Die "Cannot reformat staging. git clang-format failed"
  422. fi
  423. }
  424. # Reformat all commits of a branch (compared with master) and rewrites
  425. # the history with the formatted commits one-by-one.
  426. # This is helpful for quickly reformatting branches with multiple commits,
  427. # or where the master version of a file has been reformatted.
  428. #
  429. # You can achieve the same manually by git checkout -n <commit>, git clang-format
  430. # for each commit in your branch.
  431. function ReformatCommitsOnBranch {
  432. # Do not allow rewriting of master.
  433. # CheckBranch below will also tell us there are no changes compared with
  434. # master, but let's make this foolproof and explicit here.
  435. local current_branch=$(git rev-parse --abbrev-ref HEAD)
  436. if [ "$current_branch" == "master" ]; then
  437. Die "Must not rewrite master branch history."
  438. fi
  439. CheckBranch "--quiet"
  440. if [ $? -eq 0 ]; then
  441. echo "no modified files to format"
  442. else
  443. # Only rewrite if there are changes
  444. # Squelch warning. Our usage of git filter-branch is limited and should be ok.
  445. # Should investigate using git-filter-repo in the future instead.
  446. export FILTER_BRANCH_SQUELCH_WARNING=1
  447. # Find first commit on branch. Use $first_commit^ if you need the
  448. # commit on master we branched off.
  449. local first_commit=$(FirstCommitOfBranch)
  450. echo "First commit on branch: $first_commit"
  451. # Use --force in case it's run a second time on the same branch
  452. git filter-branch --force --tree-filter "$GIT_CLANG_FORMAT $first_commit^" -- $first_commit..HEAD
  453. if [ $? -ne 0 ]; then
  454. Die "Cannot rewrite branch. git filter-branch failed"
  455. fi
  456. fi
  457. }
  458. if [ $# -eq 0 ]; then
  459. echo "$USAGE";
  460. Die "Missing arguments. Call with one argument"
  461. fi
  462. SetTopLevelDir
  463. RequireProgram GIT git
  464. # ubuntu uses clang-format-{version} name for newer versions. fedora not.
  465. RequireProgram GIT_CLANG_FORMAT git-clang-format-11 git-clang-format-10 git-clang-format-9 git-clang-format
  466. GIT_CLANG_FORMAT_BINARY=clang-format
  467. if [[ $GIT_CLANG_FORMAT =~ .*git-clang-format-11$ ]]; then
  468. # default binary is clang-format, specify the correct version.
  469. # Alternative: git config clangformat.binary "clang-format-11"
  470. GIT_CLANG_FORMAT_BINARY="clang-format-11"
  471. elif [[ $GIT_CLANG_FORMAT =~ .*git-clang-format-10$ ]]; then
  472. # default binary is clang-format, specify the correct version.
  473. # Alternative: git config clangformat.binary "clang-format-10"
  474. GIT_CLANG_FORMAT_BINARY="clang-format-10"
  475. elif [[ $GIT_CLANG_FORMAT =~ .*git-clang-format-9$ ]]; then
  476. # default binary is clang-format, specify the correct version.
  477. # Alternative: git config clangformat.binary "clang-format-9"
  478. GIT_CLANG_FORMAT_BINARY="clang-format-9"
  479. elif [[ $GIT_CLANG_FORMAT =~ .*git-clang-format$ ]]; then
  480. Debug "Using regular clang-format"
  481. else
  482. Debug "Internal: unhandled clang-format version"
  483. fi
  484. # enforce minimal clang-format version as required by .clang-format
  485. clang_format_version=$($GIT_CLANG_FORMAT_BINARY --version | sed 's/.*clang-format version \([0-9]*\.[0-9]*\.[0-9]*\).*/\1/')
  486. Debug "Found clang-format version: $clang_format_version"
  487. clang_format_version_major=$(echo $clang_format_version | sed 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\).*/\1/')
  488. Debug "clang-format version major: $clang_format_version_major"
  489. if [ $((clang_format_version_major + 0)) -lt $((CLANG_FORMAT_REQUIRED_VERSION + 0)) ]; then
  490. Die "Require clang version $CLANG_FORMAT_REQUIRED_VERSION, found $clang_format_version_major ($clang_format_version)."
  491. fi
  492. # overwite git-clang-version for --diffstat as upstream does not have that yet
  493. RequireProgram GIT_CLANG_FORMAT_DIFFSTAT scripts/git-clang-format-custom
  494. if [ "$GIT_CLANG_FORMAT_BINARY" != "clang-format" ]; then
  495. GIT_CLANG_FORMAT="$GIT_CLANG_FORMAT --binary $GIT_CLANG_FORMAT_BINARY"
  496. GIT_CLANG_FORMAT_DIFFSTAT="$GIT_CLANG_FORMAT_DIFFSTAT --binary $GIT_CLANG_FORMAT_BINARY"
  497. fi
  498. Debug "Using $GIT_CLANG_FORMAT"
  499. Debug "Using $GIT_CLANG_FORMAT_DIFFSTAT"
  500. command_rc=0
  501. command=$1
  502. case $command in
  503. branch)
  504. shift;
  505. ReformatBranch "$@";
  506. ;;
  507. check-branch)
  508. shift;
  509. CheckBranch "$@";
  510. command_rc=$?;
  511. ;;
  512. cached)
  513. shift;
  514. ReformatCached "$@";
  515. ;;
  516. commit)
  517. shift;
  518. ReformatCommit "$@";
  519. ;;
  520. rewrite-branch)
  521. ReformatCommitsOnBranch
  522. ;;
  523. help)
  524. shift;
  525. HelpCommand $1;
  526. ;;
  527. -h|--help)
  528. echo "$USAGE";
  529. ;;
  530. *)
  531. Die "$EXEC: '$command' is not a command. See '$EXEC --help'"
  532. ;;
  533. esac
  534. ExitWith $command_rc