Skip to content

Commit

Permalink
multifile grep: perform greps in series
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
nasamuffin committed Apr 3, 2020
1 parent 8ead034 commit 3344748
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 2 deletions.
59 changes: 57 additions & 2 deletions git-secrets
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,71 @@ git_grep() {
local files=("${@}") combined_patterns=$(load_combined_patterns)

[ -z "${combined_patterns}" ] && return 1
GREP_OPTIONS= LC_ALL=C git grep -nwHEI ${options} "${combined_patterns}" -- "${files[@]}"

if [ ${#files[@]} -eq 0 ]; then
GREP_OPTIONS= LC_ALL=C git grep -nwHEI ${options} "${combined_patterns}"
return $?
fi

# let xargs watch for system limit on arg count for us. xargs returns 123 if
# any call returned 1, but we care that all calls returned 1, so invert the
# output - xargs will return 0 if every call returned 0
printf "%s\n" "${files[@]}" |
GREP_OPTIONS= LC_ALL=C xargs -P "$(nproc)" -d'\n' sh -c \
'git grep "$@"
rc=$?
case $rc in
0) exit 1 ;;
1) exit 0 ;;
*) exit 255 ;;
esac' - \
-nwHEI "${options}" "${combined_patterns}" --
status=$?

# convert the xargs-ified return code to what grep would have returned
case $status in
0) return 1 ;;
123) return 0 ;;
*) return 2 ;;
esac
}

# Performs a regular grep, taking into account patterns and recursion.
# Note: this function returns 1 on success, 0 on error.
regular_grep() {
local files=("${@}") patterns=$(load_patterns) action='skip'
local status=0
[ -z "${patterns}" ] && return 1
[ ${RECURSIVE} -eq 1 ] && action="recurse"
GREP_OPTIONS= LC_ALL=C grep -d "${action}" -nwHEI "${patterns}" "${files[@]}"

if [ "${#files[@]}" -eq 1 ] && [ "${files[0]}" = "-" ]; then
GREP_OPTIONS= LC_ALL=C grep -d "${action}" -nwHEI "${patterns}" -
return $?
fi

# let xargs watch for system limit on arg count for us.
# massage output so that xargs returns:
# 0 if all calls succeeded ("no match")
# 123 if any call failed with status 1-128 ("found a match")
# 124 if any call failed with status 255 ("error")
printf "%s\n" "${files[@]}" |
GREP_OPTIONS= LC_ALL=C xargs -P "$(nproc)" -d'\n' sh -c \
'grep "$@"
rc=$?
case $rc in
0) exit 1 ;;
1) exit 0 ;;
*) exit 255 ;;
esac' - \
-d "${action}" -nwHEI "${patterns}"
status=$?

# convert the xargs-ified return code to what grep would have returned
case $status in
0) return 1 ;;
123) return 0 ;;
*) return 2 ;;
esac
}

# Process the given status ($1) and output variables ($2).
Expand Down
17 changes: 17 additions & 0 deletions test/pre-commit.bats
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,20 @@ load test_helper
[ "${lines[1]}" == "failure1.txt:1:another line... forbidden" ]
[ "${lines[2]}" == "failure2.txt:1:me" ]
}

@test "Runs safely with args beyond the system argument length limit" {
setup_good_repo
repo_run git-secrets --install $TEST_REPO
cd $TEST_REPO

FILENAME_LENGTH="$(getconf NAME_MAX .)"
(( FILE_COUNT = ( "$(getconf ARG_MAX)" / "$FILENAME_LENGTH" ) + 1 ))

for (( i = 0; i < "$FILE_COUNT"; i++ )); do
>"$(printf "%0${FILENAME_LENGTH}d" "$i")"
done

run git add .
run git commit -m 'This is fine'
[ $status -eq 0 ]
}

0 comments on commit 3344748

Please sign in to comment.