blob: 3d09559837c76ed55384f31d4d738dd1bee60f24 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
#!/bin/bash
set -eu
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2020-2021 Patryk Obara <patryk.obara@gmail.com>
SCRIPT=$(basename "$0")
readonly SCRIPT
print_usage () {
echo "usage: $SCRIPT [-V|--verify|-d|--diff|-a|--amend] [<commit>]"
echo
echo "Fixes formatting of C and C++ files, that were touched by "
echo "commits since <commit>. Changes are limited only to files and "
echo "lines that were modified. If a file was moved/renamed and only "
echo "slightly modified (less than 10%), then it's not reformatted."
echo
echo "If no <commit> is passed, then default is HEAD~1 - which means "
echo "that only lines touched by the latest commit will be fixed, "
echo "therefore usage of this script is simply:"
echo
echo " $ ./$SCRIPT"
echo
echo "Files are formatted only if .clang-format file is located in one "
echo "of the parent directories of the source file."
echo
echo "Optional parameter --diff (or --verify) displays diff of what was "
echo "formatted and exits with a failure status if diff is not empty"
echo "(this option is intended for CI usage):"
echo
echo " $ ./$SCRIPT --diff"
echo
echo "Optional parameter --amend will amend the latest commit for you."
echo
echo " $ ./$SCRIPT --amend"
echo
echo "If you want to format a whole file instead then use clang-format "
echo "directly, e.g.:"
echo
echo " $ clang-format path/to/file.cpp"
}
main () {
case ${1:-} in
-h|-help|--help) print_usage ;;
-d|--diff) handle_dependencies ; shift ; format "$@" ; assert_empty_diff ;;
-V|--verify) handle_dependencies ; shift ; format "$@" ; assert_empty_diff ;;
-a|--amend) handle_dependencies ; shift ; format "$@" ; amend ;;
*) handle_dependencies ; format "$@" ; show_tip ;;
esac
}
handle_dependencies () {
assign_gnu_sed
assert_min_version git 1007010 "Use git version 1.7.10 or newer."
assert_min_version clang-format 12000000 "Use clang-format version 12.0.0 or newer."
}
SED=""
assign_gnu_sed () {
# Is sed GNU? (BSD seds don't support --version)
if sed --version &>/dev/null; then
SED="sed"
# No, but do we have gsed?
elif command -v gsed &> /dev/null; then
SED="gsed"
# No, so help the user install it and then quit.
else
echo "'sed' is not GNU and 'gsed' is not available."
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
echo "Install GNU sed with: brew install gnu-sed"
else
echo "Install GNU sed with your $OSTYPE package manager."
fi
exit 1
fi
}
assert_min_version () {
if ! check_min_version "$1" "$2" ; then
echo "$3"
exit 1
fi
}
check_min_version () {
$1 --version
$1 --version \
| "$SED" -e "s|.* \([0-9]*\)\.\([0-9]*\)\.\([0-9]*\).*|\1 \2 \3|" \
| test_version "$2"
return "${PIPESTATUS[2]}"
}
test_version () {
read -r major minor patch
local -r version=$((major * 1000000 + minor * 1000 + patch))
test "$1" -le "$version"
}
format () {
local -r since_ref=${1:-HEAD~1}
pushd "$(git rev-parse --show-toplevel)" > /dev/null
echo "Using paths relative to: $(pwd)"
find_cpp_files "$since_ref" | run_clang_format
popd > /dev/null
}
assert_empty_diff () {
if [[ -n "$(git_diff HEAD)" ]] ; then
git_diff HEAD
echo
echo "clang-format formatted some code for you."
echo
echo "Run 'git commit -a --amend' to save the result."
exit 1
fi
}
show_tip () {
if [[ -n "$(git_diff HEAD)" ]] ; then
echo
echo "clang-format formatted some code for you."
echo
echo "Run 'git diff' to see what changed."
echo "Run 'git commit -a --amend' to save the result."
exit 0
fi
}
amend () {
git commit -a --amend
}
git_diff () {
git diff --no-ext-diff "$@"
}
find_cpp_files () {
set +e
list_changed_files "$1" | grep -E "\.(h|hpp|c|cpp|cc)$"
set -e
}
list_changed_files () {
git_diff \
--no-renames \
--diff-filter=AMrcd \
--find-renames=90% \
--find-copies=90% \
--name-only \
"$1"
}
run_clang_format () {
while read -r src_file ; do
local ranges=()
while IFS=$'\n' read -r range ; do
ranges+=("$range")
done < <(git_diff_to_clang_line_range "$src_file")
if (( "${#ranges[@]}" )); then
echo "clang-format -i ${ranges[*]} \"$src_file\""
clang-format -i "${ranges[@]}" "$src_file"
fi
done
}
git_diff_to_clang_line_range () {
local -r file=$1
git_diff --ignore-space-at-eol -U0 HEAD~1 "$file" \
| grep -E "^@@" \
| filter_line_range \
| to_clang_line_range
}
# expects line in diff format: "@@ -<line range> +<line range> @@ <context>"
# where <line range> is either <line_number> or <line_number>,<offset>
#
filter_line_range () {
"$SED" -e 's|@@ .* +\([0-9]\+\),\?\([0-9]\+\)\? @@.*|\1 \2|'
}
to_clang_line_range () {
while read -r from_line offset ; do
local to_line=$(( from_line + offset ))
if [[ $from_line -gt 0 && $from_line -le $to_line ]] ; then
echo "-lines=$from_line:$to_line"
fi
done
}
main "$@"
|