diff options
Diffstat (limited to 'ci')
-rwxr-xr-x | ci/install-dependencies.sh | 12 | ||||
-rwxr-xr-x | ci/lib-travisci.sh | 129 | ||||
-rwxr-xr-x | ci/lib.sh | 191 | ||||
-rwxr-xr-x | ci/make-test-artifacts.sh | 12 | ||||
-rwxr-xr-x | ci/mount-fileshare.sh | 25 | ||||
-rwxr-xr-x | ci/print-test-failures.sh | 15 | ||||
-rwxr-xr-x | ci/run-build-and-tests.sh | 13 | ||||
-rwxr-xr-x | ci/run-linux32-build.sh | 4 | ||||
-rwxr-xr-x | ci/run-linux32-docker.sh | 2 | ||||
-rwxr-xr-x | ci/run-static-analysis.sh | 4 | ||||
-rwxr-xr-x | ci/run-test-slice.sh | 17 | ||||
-rwxr-xr-x | ci/run-windows-build.sh | 103 | ||||
-rwxr-xr-x | ci/test-documentation.sh | 7 |
13 files changed, 285 insertions, 249 deletions
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 06c3546e1e..d64667fcbf 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -3,7 +3,7 @@ # Install dependencies required to build and test Git on Linux and macOS # -. ${0%/*}/lib-travisci.sh +. ${0%/*}/lib.sh P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION @@ -34,12 +34,18 @@ linux-clang|linux-gcc) popd ;; osx-clang|osx-gcc) - brew update --quiet + brew update >/dev/null # Uncomment this if you want to run perf tests: # brew install gnu-time - brew install git-lfs gettext + test -z "$BREW_INSTALL_PACKAGES" || + brew install $BREW_INSTALL_PACKAGES brew link --force gettext brew install caskroom/cask/perforce + case "$jobname" in + osx-gcc) + brew link gcc@8 + ;; + esac ;; StaticAnalysis) sudo apt-get -q update diff --git a/ci/lib-travisci.sh b/ci/lib-travisci.sh deleted file mode 100755 index 69dff4d1ec..0000000000 --- a/ci/lib-travisci.sh +++ /dev/null @@ -1,129 +0,0 @@ -# Library of functions shared by all CI scripts - -skip_branch_tip_with_tag () { - # Sometimes, a branch is pushed at the same time the tag that points - # at the same commit as the tip of the branch is pushed, and building - # both at the same time is a waste. - # - # Travis gives a tagname e.g. v2.14.0 in $TRAVIS_BRANCH when - # the build is triggered by a push to a tag. Let's see if - # $TRAVIS_BRANCH is exactly at a tag, and if so, if it is - # different from $TRAVIS_BRANCH. That way, we can tell if - # we are building the tip of a branch that is tagged and - # we can skip the build because we won't be skipping a build - # of a tag. - - if TAG=$(git describe --exact-match "$TRAVIS_BRANCH" 2>/dev/null) && - test "$TAG" != "$TRAVIS_BRANCH" - then - echo "$(tput setaf 2)Tip of $TRAVIS_BRANCH is exactly at $TAG$(tput sgr0)" - exit 0 - fi -} - -# Save some info about the current commit's tree, so we can skip the build -# job if we encounter the same tree again and can provide a useful info -# message. -save_good_tree () { - echo "$(git rev-parse $TRAVIS_COMMIT^{tree}) $TRAVIS_COMMIT $TRAVIS_JOB_NUMBER $TRAVIS_JOB_ID" >>"$good_trees_file" - # limit the file size - tail -1000 "$good_trees_file" >"$good_trees_file".tmp - mv "$good_trees_file".tmp "$good_trees_file" -} - -# Skip the build job if the same tree has already been built and tested -# successfully before (e.g. because the branch got rebased, changing only -# the commit messages). -skip_good_tree () { - if ! good_tree_info="$(grep "^$(git rev-parse $TRAVIS_COMMIT^{tree}) " "$good_trees_file")" - then - # Haven't seen this tree yet, or no cached good trees file yet. - # Continue the build job. - return - fi - - echo "$good_tree_info" | { - read tree prev_good_commit prev_good_job_number prev_good_job_id - - if test "$TRAVIS_JOB_ID" = "$prev_good_job_id" - then - cat <<-EOF - $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0) - This commit has already been built and tested successfully by this build job. - To force a re-build delete the branch's cache and then hit 'Restart job'. - EOF - else - cat <<-EOF - $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0) - This commit's tree has already been built and tested successfully in build job $prev_good_job_number for commit $prev_good_commit. - The log of that build job is available at https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$prev_good_job_id - To force a re-build delete the branch's cache and then hit 'Restart job'. - EOF - fi - } - - exit 0 -} - -check_unignored_build_artifacts () -{ - ! git ls-files --other --exclude-standard --error-unmatch \ - -- ':/*' 2>/dev/null || - { - echo "$(tput setaf 1)error: found unignored build artifacts$(tput sgr0)" - false - } -} - -# Set 'exit on error' for all CI scripts to let the caller know that -# something went wrong. -# Set tracing executed commands, primarily setting environment variables -# and installing dependencies. -set -ex - -cache_dir="$HOME/travis-cache" -good_trees_file="$cache_dir/good-trees" - -mkdir -p "$cache_dir" - -skip_branch_tip_with_tag -skip_good_tree - -if test -z "$jobname" -then - jobname="$TRAVIS_OS_NAME-$CC" -fi - -export DEVELOPER=1 -export DEFAULT_TEST_TARGET=prove -export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" -export GIT_TEST_OPTS="--verbose-log -x --immediate" -export GIT_TEST_CLONE_2GB=YesPlease -if [ "$jobname" = linux-gcc ]; then - export CC=gcc-8 -fi - -case "$jobname" in -linux-clang|linux-gcc) - export GIT_TEST_HTTPD=YesPlease - - # The Linux build installs the defined dependency versions below. - # The OS X build installs the latest available versions. Keep that - # in mind when you encounter a broken OS X build! - export LINUX_P4_VERSION="16.2" - export LINUX_GIT_LFS_VERSION="1.5.2" - - P4_PATH="$HOME/custom/p4" - GIT_LFS_PATH="$HOME/custom/git-lfs" - export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH" - ;; -osx-clang|osx-gcc) - # t9810 occasionally fails on Travis CI OS X - # t9816 occasionally fails with "TAP out of sequence errors" on - # Travis CI OS X - export GIT_SKIP_TESTS="t9810 t9816" - ;; -GIT_TEST_GETTEXT_POISON) - export GIT_TEST_GETTEXT_POISON=YesPlease - ;; -esac diff --git a/ci/lib.sh b/ci/lib.sh new file mode 100755 index 0000000000..288a5b3884 --- /dev/null +++ b/ci/lib.sh @@ -0,0 +1,191 @@ +# Library of functions shared by all CI scripts + +skip_branch_tip_with_tag () { + # Sometimes, a branch is pushed at the same time the tag that points + # at the same commit as the tip of the branch is pushed, and building + # both at the same time is a waste. + # + # When the build is triggered by a push to a tag, $CI_BRANCH will + # have that tagname, e.g. v2.14.0. Let's see if $CI_BRANCH is + # exactly at a tag, and if so, if it is different from $CI_BRANCH. + # That way, we can tell if we are building the tip of a branch that + # is tagged and we can skip the build because we won't be skipping a + # build of a tag. + + if TAG=$(git describe --exact-match "$CI_BRANCH" 2>/dev/null) && + test "$TAG" != "$CI_BRANCH" + then + echo "$(tput setaf 2)Tip of $CI_BRANCH is exactly at $TAG$(tput sgr0)" + exit 0 + fi +} + +# Save some info about the current commit's tree, so we can skip the build +# job if we encounter the same tree again and can provide a useful info +# message. +save_good_tree () { + echo "$(git rev-parse $CI_COMMIT^{tree}) $CI_COMMIT $CI_JOB_NUMBER $CI_JOB_ID" >>"$good_trees_file" + # limit the file size + tail -1000 "$good_trees_file" >"$good_trees_file".tmp + mv "$good_trees_file".tmp "$good_trees_file" +} + +# Skip the build job if the same tree has already been built and tested +# successfully before (e.g. because the branch got rebased, changing only +# the commit messages). +skip_good_tree () { + if ! good_tree_info="$(grep "^$(git rev-parse $CI_COMMIT^{tree}) " "$good_trees_file")" + then + # Haven't seen this tree yet, or no cached good trees file yet. + # Continue the build job. + return + fi + + echo "$good_tree_info" | { + read tree prev_good_commit prev_good_job_number prev_good_job_id + + if test "$CI_JOB_ID" = "$prev_good_job_id" + then + cat <<-EOF + $(tput setaf 2)Skipping build job for commit $CI_COMMIT.$(tput sgr0) + This commit has already been built and tested successfully by this build job. + To force a re-build delete the branch's cache and then hit 'Restart job'. + EOF + else + cat <<-EOF + $(tput setaf 2)Skipping build job for commit $CI_COMMIT.$(tput sgr0) + This commit's tree has already been built and tested successfully in build job $prev_good_job_number for commit $prev_good_commit. + The log of that build job is available at $(url_for_job_id $prev_good_job_id) + To force a re-build delete the branch's cache and then hit 'Restart job'. + EOF + fi + } + + exit 0 +} + +check_unignored_build_artifacts () +{ + ! git ls-files --other --exclude-standard --error-unmatch \ + -- ':/*' 2>/dev/null || + { + echo "$(tput setaf 1)error: found unignored build artifacts$(tput sgr0)" + false + } +} + +# Clear MAKEFLAGS that may come from the outside world. +export MAKEFLAGS= + +# Set 'exit on error' for all CI scripts to let the caller know that +# something went wrong. +# Set tracing executed commands, primarily setting environment variables +# and installing dependencies. +set -ex + +if test true = "$TRAVIS" +then + CI_TYPE=travis + # When building a PR, TRAVIS_BRANCH refers to the *target* branch. Not + # what we want here. We want the source branch instead. + CI_BRANCH="${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}" + CI_COMMIT="$TRAVIS_COMMIT" + CI_JOB_ID="$TRAVIS_JOB_ID" + CI_JOB_NUMBER="$TRAVIS_JOB_NUMBER" + CI_OS_NAME="$TRAVIS_OS_NAME" + CI_REPO_SLUG="$TRAVIS_REPO_SLUG" + + cache_dir="$HOME/travis-cache" + + url_for_job_id () { + echo "https://travis-ci.org/$CI_REPO_SLUG/jobs/$1" + } + + BREW_INSTALL_PACKAGES="git-lfs gettext" + export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" + export GIT_TEST_OPTS="--verbose-log -x --immediate" + MAKEFLAGS="$MAKEFLAGS --jobs=2" +elif test -n "$SYSTEM_COLLECTIONURI" || test -n "$SYSTEM_TASKDEFINITIONSURI" +then + CI_TYPE=azure-pipelines + # We are running in Azure Pipelines + CI_BRANCH="$BUILD_SOURCEBRANCH" + CI_COMMIT="$BUILD_SOURCEVERSION" + CI_JOB_ID="$BUILD_BUILDID" + CI_JOB_NUMBER="$BUILD_BUILDNUMBER" + CI_OS_NAME="$(echo "$AGENT_OS" | tr A-Z a-z)" + test darwin != "$CI_OS_NAME" || CI_OS_NAME=osx + CI_REPO_SLUG="$(expr "$BUILD_REPOSITORY_URI" : '.*/\([^/]*/[^/]*\)$')" + CC="${CC:-gcc}" + + # use a subdirectory of the cache dir (because the file share is shared + # among *all* phases) + cache_dir="$HOME/test-cache/$SYSTEM_PHASENAME" + + url_for_job_id () { + echo "$SYSTEM_TASKDEFINITIONSURI$SYSTEM_TEAMPROJECT/_build/results?buildId=$1" + } + + BREW_INSTALL_PACKAGES=gcc@8 + export GIT_PROVE_OPTS="--timer --jobs 10 --state=failed,slow,save" + export GIT_TEST_OPTS="--verbose-log -x --write-junit-xml" + MAKEFLAGS="$MAKEFLAGS --jobs=10" + test windows_nt != "$CI_OS_NAME" || + GIT_TEST_OPTS="--no-chain-lint --no-bin-wrappers $GIT_TEST_OPTS" +else + echo "Could not identify CI type" >&2 + exit 1 +fi + +good_trees_file="$cache_dir/good-trees" + +mkdir -p "$cache_dir" + +skip_branch_tip_with_tag +skip_good_tree + +if test -z "$jobname" +then + jobname="$CI_OS_NAME-$CC" +fi + +export DEVELOPER=1 +export DEFAULT_TEST_TARGET=prove +export GIT_TEST_CLONE_2GB=YesPlease + +case "$jobname" in +linux-clang|linux-gcc) + if [ "$jobname" = linux-gcc ] + then + export CC=gcc-8 + fi + + export GIT_TEST_HTTPD=YesPlease + + # The Linux build installs the defined dependency versions below. + # The OS X build installs the latest available versions. Keep that + # in mind when you encounter a broken OS X build! + export LINUX_P4_VERSION="16.2" + export LINUX_GIT_LFS_VERSION="1.5.2" + + P4_PATH="$HOME/custom/p4" + GIT_LFS_PATH="$HOME/custom/git-lfs" + export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH" + ;; +osx-clang|osx-gcc) + if [ "$jobname" = osx-gcc ] + then + export CC=gcc-8 + fi + + # t9810 occasionally fails on Travis CI OS X + # t9816 occasionally fails with "TAP out of sequence errors" on + # Travis CI OS X + export GIT_SKIP_TESTS="t9810 t9816" + ;; +GIT_TEST_GETTEXT_POISON) + export GIT_TEST_GETTEXT_POISON=YesPlease + ;; +esac + +MAKEFLAGS="$MAKEFLAGS CC=${CC:-cc}" diff --git a/ci/make-test-artifacts.sh b/ci/make-test-artifacts.sh new file mode 100755 index 0000000000..646967481f --- /dev/null +++ b/ci/make-test-artifacts.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# +# Build Git and store artifacts for testing +# + +mkdir -p "$1" # in case ci/lib.sh decides to quit early + +. ${0%/*}/lib.sh + +make artifacts-tar ARTIFACTS_DIRECTORY="$1" + +check_unignored_build_artifacts diff --git a/ci/mount-fileshare.sh b/ci/mount-fileshare.sh new file mode 100755 index 0000000000..26b58a8096 --- /dev/null +++ b/ci/mount-fileshare.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +die () { + echo "$*" >&2 + exit 1 +} + +test $# = 4 || +die "Usage: $0 <share> <username> <password> <mountpoint>" + +mkdir -p "$4" || die "Could not create $4" + +case "$(uname -s)" in +Linux) + sudo mount -t cifs -o vers=3.0,username="$2",password="$3",dir_mode=0777,file_mode=0777,serverino "$1" "$4" + ;; +Darwin) + pass="$(echo "$3" | sed -e 's/\//%2F/g' -e 's/+/%2B/g')" && + mount -t smbfs,soft "smb://$2:$pass@${1#//}" "$4" + ;; +*) + die "No support for $(uname -s)" + ;; +esac || +die "Could not mount $4" diff --git a/ci/print-test-failures.sh b/ci/print-test-failures.sh index d55460a212..e688a26f0d 100755 --- a/ci/print-test-failures.sh +++ b/ci/print-test-failures.sh @@ -3,7 +3,7 @@ # Print output of failing tests # -. ${0%/*}/lib-travisci.sh +. ${0%/*}/lib.sh # Tracing executed commands would produce too much noise in the loop below. set +x @@ -38,6 +38,19 @@ do test_name="${TEST_EXIT%.exit}" test_name="${test_name##*/}" trash_dir="trash directory.$test_name" + case "$CI_TYPE" in + travis) + ;; + azure-pipelines) + mkdir -p failed-test-artifacts + mv "$trash_dir" failed-test-artifacts + continue + ;; + *) + echo "Unhandled CI type: $CI_TYPE" >&2 + exit 1 + ;; + esac trash_tgz_b64="trash.$test_name.base64" if [ -d "$trash_dir" ] then diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index cda170d5c2..cdd2913440 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -3,12 +3,15 @@ # Build and test Git # -. ${0%/*}/lib-travisci.sh +. ${0%/*}/lib.sh -ln -s "$cache_dir/.prove" t/.prove +case "$CI_OS_NAME" in +windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";; +*) ln -s "$cache_dir/.prove" t/.prove;; +esac -make --jobs=2 -make --quiet test +make +make test if test "$jobname" = "linux-gcc" then export GIT_TEST_SPLIT_INDEX=yes @@ -17,7 +20,7 @@ then export GIT_TEST_OE_DELTA_SIZE=5 export GIT_TEST_COMMIT_GRAPH=1 export GIT_TEST_MULTI_PACK_INDEX=1 - make --quiet test + make test fi check_unignored_build_artifacts diff --git a/ci/run-linux32-build.sh b/ci/run-linux32-build.sh index 2c60d2e70a..e3a193adbc 100755 --- a/ci/run-linux32-build.sh +++ b/ci/run-linux32-build.sh @@ -55,6 +55,6 @@ linux32 --32bit i386 su -m -l $CI_USER -c ' set -ex cd /usr/src/git test -n "$cache_dir" && ln -s "$cache_dir/.prove" t/.prove - make --jobs=2 - make --quiet test + make + make test ' diff --git a/ci/run-linux32-docker.sh b/ci/run-linux32-docker.sh index 21637903ce..751acfcf8a 100755 --- a/ci/run-linux32-docker.sh +++ b/ci/run-linux32-docker.sh @@ -3,7 +3,7 @@ # Download and run Docker image to build and test 32-bit Git # -. ${0%/*}/lib-travisci.sh +. ${0%/*}/lib.sh docker pull daald/ubuntu32:xenial diff --git a/ci/run-static-analysis.sh b/ci/run-static-analysis.sh index 5688f261d0..a19aa7ebbc 100755 --- a/ci/run-static-analysis.sh +++ b/ci/run-static-analysis.sh @@ -3,9 +3,9 @@ # Perform various static code analysis checks # -. ${0%/*}/lib-travisci.sh +. ${0%/*}/lib.sh -make --jobs=2 coccicheck +make coccicheck set +x diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh new file mode 100755 index 0000000000..f8c2c3106a --- /dev/null +++ b/ci/run-test-slice.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# +# Test Git in parallel +# + +. ${0%/*}/lib.sh + +case "$CI_OS_NAME" in +windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";; +*) ln -s "$cache_dir/.prove" t/.prove;; +esac + +make --quiet -C t T="$(cd t && + ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh | + tr '\n' ' ')" + +check_unignored_build_artifacts diff --git a/ci/run-windows-build.sh b/ci/run-windows-build.sh deleted file mode 100755 index d99a180e52..0000000000 --- a/ci/run-windows-build.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -# -# Script to trigger the Git for Windows build and test run. -# Set the $GFW_CI_TOKEN as environment variable. -# Pass the branch (only branches on https://github.com/git/git are -# supported) and a commit hash. -# - -. ${0%/*}/lib-travisci.sh - -test $# -ne 2 && echo "Unexpected number of parameters" && exit 1 -test -z "$GFW_CI_TOKEN" && echo "GFW_CI_TOKEN not defined" && exit - -BRANCH=$1 -COMMIT=$2 - -gfwci () { - local CURL_ERROR_CODE HTTP_CODE - CONTENT_FILE=$(mktemp -t "git-windows-ci-XXXXXX") - while test -z $HTTP_CODE - do - HTTP_CODE=$(curl \ - -H "Authentication: Bearer $GFW_CI_TOKEN" \ - --silent --retry 5 --write-out '%{HTTP_CODE}' \ - --output >(sed "$(printf '1s/^\xef\xbb\xbf//')" >$CONTENT_FILE) \ - "https://git-for-windows-ci.azurewebsites.net/api/TestNow?$1" \ - ) - CURL_ERROR_CODE=$? - # The GfW CI web app sometimes returns HTTP errors of - # "502 bad gateway" or "503 service unavailable". - # We also need to check the HTTP content because the GfW web - # app seems to pass through (error) results from other Azure - # calls with HTTP code 200. - # Wait a little and retry if we detect this error. More info: - # https://docs.microsoft.com/en-in/azure/app-service-web/app-service-web-troubleshoot-http-502-http-503 - if test $HTTP_CODE -eq 502 || - test $HTTP_CODE -eq 503 || - grep "502 - Web server received an invalid response" $CONTENT_FILE >/dev/null - then - sleep 10 - HTTP_CODE= - fi - done - cat $CONTENT_FILE - rm $CONTENT_FILE - if test $CURL_ERROR_CODE -ne 0 - then - return $CURL_ERROR_CODE - fi - if test "$HTTP_CODE" -ge 400 && test "$HTTP_CODE" -lt 600 - then - return 127 - fi -} - -# Trigger build job -BUILD_ID=$(gfwci "action=trigger&branch=$BRANCH&commit=$COMMIT&skipTests=false") -if test $? -ne 0 -then - echo "Unable to trigger Visual Studio Team Services Build" - echo "$BUILD_ID" - exit 1 -fi - -# Check if the $BUILD_ID contains a number -case $BUILD_ID in -''|*[!0-9]*) echo "Unexpected build number: $BUILD_ID" && exit 1 -esac - -echo "Visual Studio Team Services Build #${BUILD_ID}" - -# Tracing execued commands would produce too much noise in the waiting -# loop below. -set +x - -# Wait until build job finished -STATUS= -RESULT= -while true -do - LAST_STATUS=$STATUS - STATUS=$(gfwci "action=status&buildId=$BUILD_ID") - test "$STATUS" = "$LAST_STATUS" || printf "\nStatus: %s " "$STATUS" - printf "." - - case "$STATUS" in - inProgress|postponed|notStarted) sleep 10 ;; # continue - "completed: succeeded") RESULT="success"; break;; # success - "completed: failed") break;; # failure - *) echo "Unhandled status: $STATUS"; break;; # unknown - esac -done - -# Print log -echo "" -echo "" -set -x -gfwci "action=log&buildId=$BUILD_ID" | cut -c 30- - -# Set exit code for TravisCI -test "$RESULT" = "success" - -save_good_tree diff --git a/ci/test-documentation.sh b/ci/test-documentation.sh index a20de9ca12..be3b7d376a 100755 --- a/ci/test-documentation.sh +++ b/ci/test-documentation.sh @@ -3,15 +3,16 @@ # Perform sanity checks on documentation and build it. # -. ${0%/*}/lib-travisci.sh +. ${0%/*}/lib.sh +test -n "$ALREADY_HAVE_ASCIIDOCTOR" || gem install asciidoctor make check-builtins make check-docs # Build docs with AsciiDoc -make --jobs=2 doc > >(tee stdout.log) 2> >(tee stderr.log >&2) +make doc > >(tee stdout.log) 2> >(tee stderr.log >&2) ! test -s stderr.log test -s Documentation/git.html test -s Documentation/git.xml @@ -23,7 +24,7 @@ check_unignored_build_artifacts # Build docs with AsciiDoctor make clean -make --jobs=2 USE_ASCIIDOCTOR=1 doc > >(tee stdout.log) 2> >(tee stderr.log >&2) +make USE_ASCIIDOCTOR=1 doc > >(tee stdout.log) 2> >(tee stderr.log >&2) sed '/^GIT_VERSION = / d' stderr.log ! test -s stderr.log test -s Documentation/git.html |