diff --git a/git-secrets b/git-secrets index a2f16ac..e38db2b 100755 --- a/git-secrets +++ b/git-secrets @@ -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). diff --git a/test/pre-commit.bats b/test/pre-commit.bats index 5ace267..2c58663 100644 --- a/test/pre-commit.bats +++ b/test/pre-commit.bats @@ -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 ] +} diff --git a/test/test_helper.bash b/test/test_helper.bash index e0e93a2..325cd3c 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -1,4 +1,6 @@ #!/bin/bash +# Disallow any system-level git-secret pattern configs +export GIT_CONFIG_NOSYSTEM="true" export TEST_REPO="$BATS_TMPDIR/test-repo" export TEMP_HOME="$BATS_TMPDIR/home" export TEMPLATE_DIR="${BATS_TMPDIR}/template" @@ -31,6 +33,9 @@ setup_repo() { mkdir -p $TEST_REPO cd $TEST_REPO git init --initial-branch=master + # Uninstall any hooks present in the system template which could interfere + # with git-secrets + rm -fr .git/hooks/* git config --local --add secrets.patterns '@todo' git config --local --add secrets.patterns 'forbidden|me' git config --local --add secrets.patterns '#hash'