Skip to content

Commit 788c2f0

Browse files
committed
multifile grep: perform greps in series
Passing a very long argument list to git-grep can cause it to fail; indeed, it's possible for the list of paths passed by git-secrets to either grep or git-grep to exceed the maximum number of arguments allowed in a user's environment (`getconf ARG_MAX`). Instead, let xargs check that the number of arguments won't exceed the system limit. Signed-off-by: Emily Shaffer <[email protected]>
1 parent 8ead034 commit 788c2f0

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

git-secrets

+47-2
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,63 @@ scan_history() {
111111
git_grep() {
112112
local options="$1"; shift
113113
local files=("${@}") combined_patterns=$(load_combined_patterns)
114+
local status=0
114115

115116
[ -z "${combined_patterns}" ] && return 1
116-
GREP_OPTIONS= LC_ALL=C git grep -nwHEI ${options} "${combined_patterns}" -- "${files[@]}"
117+
118+
if [ ${#files[@]} -eq 0 ]; then
119+
GREP_OPTIONS= LC_ALL=C git grep -nwHEI ${options} "${combined_patterns}"
120+
return $?
121+
fi
122+
123+
# let xargs watch for system limit on arg count for us. xargs returns 123 if
124+
# any call returned 1, but we care that all calls returned 1, so invert the
125+
# output - xargs will return 0 if every call returned 0
126+
printf "%s\n" "${files[@]}" |
127+
GREP_OPTIONS= LC_ALL=C xargs -P "$(nproc)" -d'\n' sh -c \
128+
'git grep "$@"; [ $? -eq 1 ]' - \
129+
-nwHEI "${options}" "${combined_patterns}" --
130+
131+
# uninvert xargs's 0, which means all git grep invocations returned 1.
132+
[ "$?" -ne 0 ]
117133
}
118134

119135
# Performs a regular grep, taking into account patterns and recursion.
120136
# Note: this function returns 1 on success, 0 on error.
121137
regular_grep() {
122138
local files=("${@}") patterns=$(load_patterns) action='skip'
139+
local status=0
123140
[ -z "${patterns}" ] && return 1
124141
[ ${RECURSIVE} -eq 1 ] && action="recurse"
125-
GREP_OPTIONS= LC_ALL=C grep -d "${action}" -nwHEI "${patterns}" "${files[@]}"
142+
143+
if [ "${files[@]}" = "-" ]; then
144+
GREP_OPTIONS= LC_ALL=C grep -d "${action}" -nwHEI "${patterns}" -
145+
return $?
146+
fi
147+
148+
# let xargs watch for system limit on arg count for us. xargs returns 123 if
149+
# any call returned 1-128, and 124 if any call returned 255. we care that all
150+
# calls returned 1 (no result found), and grep returns 2 in an error; convert
151+
# the error to one xargs can recognize and invert the result otherwise.
152+
printf "%s\n" "${files[@]}" |
153+
GREP_OPTIONS= LC_ALL=C xargs -P "$(nproc)" -d'\n' sh -c \
154+
'grep "$@"
155+
rc=$?
156+
if [ $rc -eq 2 ]; then
157+
exit 255
158+
fi
159+
[ $rc -eq 1 ]' - \
160+
-d "${action}" -nwHEI "${patterns}"
161+
status=$?
162+
163+
# we fudged the output of grep to return 255 on error instead of 2; xargs
164+
# reports a 255 as 124
165+
if [ $status -eq 124 ]; then
166+
return 2
167+
fi
168+
169+
# xargs returns 0 if all invocations returned 0, and we inverted the output
170+
[ $status -ne 0 ]
126171
}
127172

128173
# Process the given status ($1) and output variables ($2).

test/pre-commit.bats

+17
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,20 @@ load test_helper
6060
[ "${lines[1]}" == "failure1.txt:1:another line... forbidden" ]
6161
[ "${lines[2]}" == "failure2.txt:1:me" ]
6262
}
63+
64+
@test "Runs safely with args beyond the system argument length limit" {
65+
setup_good_repo
66+
repo_run git-secrets --install $TEST_REPO
67+
cd $TEST_REPO
68+
69+
FILENAME_LENGTH="$(getconf NAME_MAX .)"
70+
(( FILE_COUNT = "$(getconf ARG_MAX)" / "$FILENAME_LENGTH" ))
71+
72+
for (( i = 0; i < "$FILE_COUNT"; i++ )); do
73+
>"$(printf "%0${FILENAME_LENGTH}d" "$i")"
74+
done
75+
76+
run git add .
77+
run git commit -m 'This is fine'
78+
[ $status -eq 0 ]
79+
}

0 commit comments

Comments
 (0)