Merge branch 'js/vsts-ci'
authorJunio C Hamano <gitster@pobox.com>
Thu, 7 Feb 2019 06:05:26 +0000 (22:05 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 7 Feb 2019 06:05:26 +0000 (22:05 -0800)
Prepare to run test suite on Azure Pipeline.

* js/vsts-ci: (22 commits)
test-date: drop unused parameter to getnanos()
ci: parallelize testing on Windows
ci: speed up Windows phase
tests: optionally skip bin-wrappers/
t0061: workaround issues with --with-dashes and RUNTIME_PREFIX
tests: add t/helper/ to the PATH with --with-dashes
mingw: try to work around issues with the test cleanup
tests: include detailed trace logs with --write-junit-xml upon failure
tests: avoid calling Perl just to determine file sizes
README: add a build badge (status of the Azure Pipelines build)
mingw: be more generous when wrapping up the setitimer() emulation
ci: use git-sdk-64-minimal build artifact
ci: add a Windows job to the Azure Pipelines definition
Add a build definition for Azure DevOps
ci/lib.sh: add support for Azure Pipelines
tests: optionally write results as JUnit-style .xml
test-date: add a subcommand to measure times in shell scripts
ci: use a junction on Windows instead of a symlink
ci: inherit --jobs via MAKEFLAGS in run-build-and-tests
ci/lib.sh: encapsulate Travis-specific things
...

12 files changed:
1  2 
Makefile
ci/install-dependencies.sh
ci/lib.sh
ci/run-build-and-tests.sh
ci/run-linux32-build.sh
compat/mingw.c
t/README
t/helper/test-date.c
t/helper/test-tool.c
t/helper/test-tool.h
t/t0061-run-command.sh
t/test-lib.sh
diff --combined Makefile
index afa411d727532f95278438851a5ad09292fb7f48,daa318fe1727a4d528c873ff4d75c329df5fd797..45e6d700a73b52746e54c69c69ca7f6dcd9cb112
+++ b/Makefile
@@@ -186,12 -186,6 +186,12 @@@ all:
  # in one call to the platform's SHA1_Update(). e.g. APPLE_COMMON_CRYPTO
  # wants 'SHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' defined.
  #
 +# Define BLK_SHA256 to use the built-in SHA-256 routines.
 +#
 +# Define GCRYPT_SHA256 to use the SHA-256 routines in libgcrypt.
 +#
 +# Define OPENSSL_SHA256 to use the SHA-256 routines in OpenSSL.
 +#
  # Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin).
  #
  # Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin).
@@@ -634,6 -628,7 +634,6 @@@ SCRIPT_LIB += git-parse-remot
  SCRIPT_LIB += git-rebase--am
  SCRIPT_LIB += git-rebase--common
  SCRIPT_LIB += git-rebase--preserve-merges
 -SCRIPT_LIB += git-rebase--merge
  SCRIPT_LIB += git-sh-setup
  SCRIPT_LIB += git-sh-i18n
  
@@@ -689,7 -684,6 +689,7 @@@ SCRIPTS = $(SCRIPT_SH_INS) 
  
  ETAGS_TARGET = TAGS
  
 +FUZZ_OBJS += fuzz-commit-graph.o
  FUZZ_OBJS += fuzz-pack-headers.o
  FUZZ_OBJS += fuzz-pack-idx.o
  
@@@ -730,9 -724,7 +730,9 @@@ TEST_BUILTINS_OBJS += test-dump-split-i
  TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
  TEST_BUILTINS_OBJS += test-example-decorate.o
  TEST_BUILTINS_OBJS += test-genrandom.o
 +TEST_BUILTINS_OBJS += test-hash.o
  TEST_BUILTINS_OBJS += test-hashmap.o
 +TEST_BUILTINS_OBJS += test-hash-speed.o
  TEST_BUILTINS_OBJS += test-index-version.o
  TEST_BUILTINS_OBJS += test-json-writer.o
  TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
@@@ -755,7 -747,6 +755,7 @@@ TEST_BUILTINS_OBJS += test-run-command.
  TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
  TEST_BUILTINS_OBJS += test-sha1.o
  TEST_BUILTINS_OBJS += test-sha1-array.o
 +TEST_BUILTINS_OBJS += test-sha256.o
  TEST_BUILTINS_OBJS += test-sigchain.o
  TEST_BUILTINS_OBJS += test-strcmp-offset.o
  TEST_BUILTINS_OBJS += test-string-list.o
@@@ -763,6 -754,7 +763,7 @@@ TEST_BUILTINS_OBJS += test-submodule-co
  TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
  TEST_BUILTINS_OBJS += test-subprocess.o
  TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
+ TEST_BUILTINS_OBJS += test-xml-encode.o
  TEST_BUILTINS_OBJS += test-wildmatch.o
  TEST_BUILTINS_OBJS += test-windows-named-pipe.o
  TEST_BUILTINS_OBJS += test-write-cache.o
@@@ -1655,19 -1647,6 +1656,19 @@@ endi
  endif
  endif
  
 +ifdef OPENSSL_SHA256
 +      EXTLIBS += $(LIB_4_CRYPTO)
 +      BASIC_CFLAGS += -DSHA256_OPENSSL
 +else
 +ifdef GCRYPT_SHA256
 +      BASIC_CFLAGS += -DSHA256_GCRYPT
 +      EXTLIBS += -lgcrypt
 +else
 +      LIB_OBJS += sha256/block/sha256.o
 +      BASIC_CFLAGS += -DSHA256_BLK
 +endif
 +endif
 +
  ifdef SHA1_MAX_BLOCK_SIZE
        LIB_OBJS += compat/sha1-chunked.o
        BASIC_CFLAGS += -DSHA1_MAX_BLOCK_SIZE="$(SHA1_MAX_BLOCK_SIZE)"
@@@ -2948,6 -2927,16 +2949,16 @@@ rpm:
        @false
  .PHONY: rpm
  
+ artifacts-tar:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) \
+               GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
+               $(NO_INSTALL) $(MOFILES)
+       $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
+               SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
+       test -n "$(ARTIFACTS_DIRECTORY)"
+       mkdir -p "$(ARTIFACTS_DIRECTORY)"
+       $(TAR) czf "$(ARTIFACTS_DIRECTORY)/artifacts.tar.gz" $^ templates/blt/
+ .PHONY: artifacts-tar
  htmldocs = git-htmldocs-$(GIT_VERSION)
  manpages = git-manpages-$(GIT_VERSION)
  .PHONY: dist-doc distclean
@@@ -3125,7 -3114,7 +3136,7 @@@ cover_db_html: cover_d
  # An example command to build against libFuzzer from LLVM 4.0.0:
  #
  # make CC=clang CXX=clang++ \
 -#      FUZZ_CXXFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \
 +#      CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \
  #      LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \
  #      fuzz-all
  #
index dc719876bb12309c086952d275085d8d3d0690e6,bcdcc715924c53bbcf1418b7ddeee426c94468b8..608ff964dee0c15c2c2a97c15fc5a86d5138488a
@@@ -3,7 -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
@@@ -37,14 -37,10 +37,15 @@@ osx-clang|osx-gcc
        brew update --quiet
        # 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 --combined ci/lib.sh
index 0000000000000000000000000000000000000000,c2bc6c68b9fef34be4e19cbb0b3ca94f794fba68..16f4ecbc67196b19f45d4e5c2273b8ea35e0895d
mode 000000,100755..100755
--- /dev/null
+++ b/ci/lib.sh
@@@ -1,0 -1,179 +1,188 @@@
 -      BREW_INSTALL_PACKAGES=
+ # 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
+       }
+ }
+ # 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"
+       export 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"
+       }
 -if [ "$jobname" = linux-gcc ]; then
 -      export CC=gcc-8
 -fi
++      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"
+       export 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
++
++export MAKEFLAGS="CC=${CC:-cc}"
index 84431c097eb8dffa1b540ee61d6c047239afd18a,74d838ea01883031246cb83342c5daaa9545333a..cdd291344047394b26c17635d4507ad7ce14b542
@@@ -3,12 -3,15 +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
 -make --quiet test
 +make test
  if test "$jobname" = "linux-gcc"
  then
        export GIT_TEST_SPLIT_INDEX=yes
@@@ -17,7 -20,7 +20,7 @@@
        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 --combined ci/run-linux32-build.sh
index 26c168a0165522655e4e80988ba553b314b333f5,09e9276e1276e089fdde09da4cb89f15404be416..e3a193adbce39cb62b3be20b269444188a900613
@@@ -55,6 -55,6 +55,6 @@@ linux32 --32bit i386 su -m -l $CI_USER 
        set -ex
        cd /usr/src/git
        test -n "$cache_dir" && ln -s "$cache_dir/.prove" t/.prove
-       make --jobs=2
+       make
 -      make --quiet test
 +      make test
  '
diff --combined compat/mingw.c
index 0af86840197deb3b0b9d442e3e5236170c31a4de,e0dfe8844d416c7efab0a037cbf3a045b772e39a..4276297595e21064abe2c0fb496f90d39455e200
@@@ -7,7 -7,6 +7,7 @@@
  #include "../cache.h"
  #include "win32/lazyload.h"
  #include "../config.h"
 +#include "dir.h"
  
  #define HCAST(type, handle) ((type)(intptr_t)handle)
  
@@@ -1032,7 -1031,7 +1032,7 @@@ char *mingw_getcwd(char *pointer, int l
   * See "Parsing C++ Command-Line Arguments" at Microsoft's Docs:
   * https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
   */
 -static const char *quote_arg(const char *arg)
 +static const char *quote_arg_msvc(const char *arg)
  {
        /* count chars to quote */
        int len = 0, n = 0;
        return q;
  }
  
 +#include "quote.h"
 +
 +static const char *quote_arg_msys2(const char *arg)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      const char *p2 = arg, *p;
 +
 +      for (p = arg; *p; p++) {
 +              int ws = isspace(*p);
 +              if (!ws && *p != '\\' && *p != '"' && *p != '{')
 +                      continue;
 +              if (!buf.len)
 +                      strbuf_addch(&buf, '"');
 +              if (p != p2)
 +                      strbuf_add(&buf, p2, p - p2);
 +              if (!ws && *p != '{')
 +                      strbuf_addch(&buf, '\\');
 +              p2 = p;
 +      }
 +
 +      if (p == arg)
 +              strbuf_addch(&buf, '"');
 +      else if (!buf.len)
 +              return arg;
 +      else
 +              strbuf_add(&buf, p2, p - p2),
 +
 +      strbuf_addch(&buf, '"');
 +      return strbuf_detach(&buf, 0);
 +}
 +
  static const char *parse_interpreter(const char *cmd)
  {
        static char buf[100];
@@@ -1349,47 -1317,6 +1349,47 @@@ struct pinfo_t 
  static struct pinfo_t *pinfo = NULL;
  CRITICAL_SECTION pinfo_cs;
  
 +/* Used to match and chomp off path components */
 +static inline int match_last_path_component(const char *path, size_t *len,
 +                                          const char *component)
 +{
 +      size_t component_len = strlen(component);
 +      if (*len < component_len + 1 ||
 +          !is_dir_sep(path[*len - component_len - 1]) ||
 +          fspathncmp(path + *len - component_len, component, component_len))
 +              return 0;
 +      *len -= component_len + 1;
 +      /* chomp off repeated dir separators */
 +      while (*len > 0 && is_dir_sep(path[*len - 1]))
 +              (*len)--;
 +      return 1;
 +}
 +
 +static int is_msys2_sh(const char *cmd)
 +{
 +      if (cmd && !strcmp(cmd, "sh")) {
 +              static int ret = -1;
 +              char *p;
 +
 +              if (ret >= 0)
 +                      return ret;
 +
 +              p = path_lookup(cmd, 0);
 +              if (!p)
 +                      ret = 0;
 +              else {
 +                      size_t len = strlen(p);
 +
 +                      ret = match_last_path_component(p, &len, "sh.exe") &&
 +                              match_last_path_component(p, &len, "bin") &&
 +                              match_last_path_component(p, &len, "usr");
 +                      free(p);
 +              }
 +              return ret;
 +      }
 +      return 0;
 +}
 +
  static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv,
                              const char *dir,
                              int prepend_cmd, int fhin, int fhout, int fherr)
        unsigned flags = CREATE_UNICODE_ENVIRONMENT;
        BOOL ret;
        HANDLE cons;
 +      const char *(*quote_arg)(const char *arg) =
 +              is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
  
        do_unset_environment_variables();
  
@@@ -2175,7 -2100,7 +2175,7 @@@ static void stop_timer_thread(void
        if (timer_event)
                SetEvent(timer_event);  /* tell thread to terminate */
        if (timer_thread) {
-               int rc = WaitForSingleObject(timer_thread, 1000);
+               int rc = WaitForSingleObject(timer_thread, 10000);
                if (rc == WAIT_TIMEOUT)
                        error("timer thread did not terminate timely");
                else if (rc != WAIT_OBJECT_0)
diff --combined t/README
index 6140b8c45bc7890c9166d44b852782364fa675ca,063530234f3ed2b160ee3ebc7acc84c83615ba5d..1326fd7505fde3a9e34fef1e197623c8d409d6ff
+++ b/t/README
@@@ -170,6 -170,15 +170,15 @@@ appropriately before running "make"
        implied by other options like --valgrind and
        GIT_TEST_INSTALLED.
  
+ --no-bin-wrappers::
+       By default, the test suite uses the wrappers in
+       `../bin-wrappers/` to execute `git` and friends. With this option,
+       `../git` and friends are run directly. This is not recommended
+       in general, as the wrappers contain safeguards to ensure that no
+       files from an installed Git are used, but can speed up test runs
+       especially on platforms where running shell scripts is expensive
+       (most notably, Windows).
  --root=<directory>::
        Create "trash" directories used to store all temporary data during
        testing under <directory>, instead of the t/ directory.
@@@ -358,10 -367,6 +367,10 @@@ GIT_TEST_INDEX_VERSION=<n> exercises th
  for the index version specified.  Can be set to any valid version
  (currently 2, 3, or 4).
  
 +GIT_TEST_PACK_SPARSE=<boolean> if enabled will default the pack-objects
 +builtin to use the sparse object walk. This can still be overridden by
 +the --no-sparse command-line argument.
 +
  GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path
  by overriding the minimum number of cache entries required per thread.
  
@@@ -378,11 -383,6 +387,11 @@@ GIT_TEST_MULTI_PACK_INDEX=<boolean>, wh
  index to be written after every 'git repack' command, and overrides the
  'core.multiPackIndex' setting to true.
  
 +GIT_TEST_SIDEBAND_ALL=<boolean>, when true, overrides the
 +'uploadpack.allowSidebandAll' setting to true, and when false, forces
 +fetch-pack to not request sideband-all (even if the server advertises
 +sideband-all).
 +
  Naming Tests
  ------------
  
diff --combined t/helper/test-date.c
index a47bfa3003ba72d9ccac56981bf2ed89302af802,f9e2b91ed1b3dbe4efa2d1d540cabb5dce171b57..b3253803ac8ce66625e346fcbce3ca9c9a16ae55
@@@ -3,11 -3,11 +3,12 @@@
  
  static const char *usage_msg = "\n"
  "  test-tool date relative [time_t]...\n"
 +"  test-tool date human [time_t]...\n"
  "  test-tool date show:<format> [time_t]...\n"
  "  test-tool date parse [date]...\n"
  "  test-tool date approxidate [date]...\n"
  "  test-tool date timestamp [date]...\n"
+ "  test-tool date getnanos [start-nanos]\n"
  "  test-tool date is64bit\n"
  "  test-tool date time_t-is64bit\n";
  
@@@ -17,20 -17,12 +18,20 @@@ static void show_relative_dates(const c
  
        for (; *argv; argv++) {
                time_t t = atoi(*argv);
 -              show_date_relative(t, 0, now, &buf);
 +              show_date_relative(t, now, &buf);
                printf("%s -> %s\n", *argv, buf.buf);
        }
        strbuf_release(&buf);
  }
  
 +static void show_human_dates(const char **argv)
 +{
 +      for (; *argv; argv++) {
 +              time_t t = atoi(*argv);
 +              printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(HUMAN)));
 +      }
 +}
 +
  static void show_dates(const char **argv, const char *format)
  {
        struct date_mode mode;
@@@ -91,12 -83,21 +92,21 @@@ static void parse_approx_timestamp(cons
        }
  }
  
+ static void getnanos(const char **argv)
+ {
+       double seconds = getnanotime() / 1.0e9;
+       if (*argv)
+               seconds -= strtod(*argv, NULL);
+       printf("%lf\n", seconds);
+ }
  int cmd__date(int argc, const char **argv)
  {
        struct timeval now;
        const char *x;
  
 -      x = getenv("TEST_DATE_NOW");
 +      x = getenv("GIT_TEST_DATE_NOW");
        if (x) {
                now.tv_sec = atoi(x);
                now.tv_usec = 0;
                usage(usage_msg);
        if (!strcmp(*argv, "relative"))
                show_relative_dates(argv+1, &now);
 +      else if (!strcmp(*argv, "human"))
 +              show_human_dates(argv+1);
        else if (skip_prefix(*argv, "show:", &x))
                show_dates(argv+1, x);
        else if (!strcmp(*argv, "parse"))
                parse_approxidate(argv+1, &now);
        else if (!strcmp(*argv, "timestamp"))
                parse_approx_timestamp(argv+1, &now);
+       else if (!strcmp(*argv, "getnanos"))
+               getnanos(argv+1);
        else if (!strcmp(*argv, "is64bit"))
                return sizeof(timestamp_t) == 8 ? 0 : 1;
        else if (!strcmp(*argv, "time_t-is64bit"))
diff --combined t/helper/test-tool.c
index 5b137874e1d21166c92d00f540f6ecd68c18780a,4b4b397d9365e3cc594702a5ccc4f28e8c5f8b6d..50c55f8b1a3baf2df88214824b01fb707966ebc7
@@@ -20,7 -20,6 +20,7 @@@ static struct test_cmd cmds[] = 
        { "example-decorate", cmd__example_decorate },
        { "genrandom", cmd__genrandom },
        { "hashmap", cmd__hashmap },
 +      { "hash-speed", cmd__hash_speed },
        { "index-version", cmd__index_version },
        { "json-writer", cmd__json_writer },
        { "lazy-init-name-hash", cmd__lazy_init_name_hash },
@@@ -43,7 -42,6 +43,7 @@@
        { "scrap-cache-tree", cmd__scrap_cache_tree },
        { "sha1", cmd__sha1 },
        { "sha1-array", cmd__sha1_array },
 +      { "sha256", cmd__sha256 },
        { "sigchain", cmd__sigchain },
        { "strcmp-offset", cmd__strcmp_offset },
        { "string-list", cmd__string_list },
@@@ -51,6 -49,7 +51,7 @@@
        { "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
        { "subprocess", cmd__subprocess },
        { "urlmatch-normalization", cmd__urlmatch_normalization },
+       { "xml-encode", cmd__xml_encode },
        { "wildmatch", cmd__wildmatch },
  #ifdef GIT_WINDOWS_NATIVE
        { "windows-named-pipe", cmd__windows_named_pipe },
diff --combined t/helper/test-tool.h
index a396c10947e86bb88e47a8e4df4fef2de64024e8,c0ab65e370eb6eec7e783013bdfe541ea4c29ab5..a563df49bf086b92eb24b3f90f6194eed059de81
@@@ -1,7 -1,6 +1,7 @@@
  #ifndef TEST_TOOL_H
  #define TEST_TOOL_H
  
 +#define USE_THE_INDEX_COMPATIBILITY_MACROS
  #include "git-compat-util.h"
  
  int cmd__chmtime(int argc, const char **argv);
@@@ -17,7 -16,6 +17,7 @@@ int cmd__dump_untracked_cache(int argc
  int cmd__example_decorate(int argc, const char **argv);
  int cmd__genrandom(int argc, const char **argv);
  int cmd__hashmap(int argc, const char **argv);
 +int cmd__hash_speed(int argc, const char **argv);
  int cmd__index_version(int argc, const char **argv);
  int cmd__json_writer(int argc, const char **argv);
  int cmd__lazy_init_name_hash(int argc, const char **argv);
@@@ -40,7 -38,6 +40,7 @@@ int cmd__run_command(int argc, const ch
  int cmd__scrap_cache_tree(int argc, const char **argv);
  int cmd__sha1(int argc, const char **argv);
  int cmd__sha1_array(int argc, const char **argv);
 +int cmd__sha256(int argc, const char **argv);
  int cmd__sigchain(int argc, const char **argv);
  int cmd__strcmp_offset(int argc, const char **argv);
  int cmd__string_list(int argc, const char **argv);
@@@ -48,12 -45,11 +48,13 @@@ int cmd__submodule_config(int argc, con
  int cmd__submodule_nested_repo_config(int argc, const char **argv);
  int cmd__subprocess(int argc, const char **argv);
  int cmd__urlmatch_normalization(int argc, const char **argv);
+ int cmd__xml_encode(int argc, const char **argv);
  int cmd__wildmatch(int argc, const char **argv);
  #ifdef GIT_WINDOWS_NATIVE
  int cmd__windows_named_pipe(int argc, const char **argv);
  #endif
  int cmd__write_cache(int argc, const char **argv);
  
 +int cmd_hash_impl(int ac, const char **av, int algo);
 +
  #endif
diff --combined t/t0061-run-command.sh
index 9c7604dcabec86776c3bb25598e39dac79118765,5a2d087bf094e4af12b600c74a4f2cef0dc3a018..ebc49561acc08e916b2ab78cd84e6729c65f5803
@@@ -166,7 -166,8 +166,8 @@@ test_trace () 
        expect="$1"
        shift
        GIT_TRACE=1 test-tool run-command "$@" run-command true 2>&1 >/dev/null | \
-               sed -e 's/.* run_command: //' -e '/trace: .*/d' >actual &&
+               sed -e 's/.* run_command: //' -e '/trace: .*/d' \
+                       -e '/RUNTIME_PREFIX requested/d' >actual &&
        echo "$expect true" >expect &&
        test_cmp expect actual
  }
@@@ -199,14 -200,4 +200,14 @@@ test_expect_success 'GIT_TRACE with env
        )
  '
  
 +test_expect_success MINGW 'verify curlies are quoted properly' '
 +      : force the rev-parse through the MSYS2 Bash &&
 +      git -c alias.r="!git rev-parse" r -- a{b}c >actual &&
 +      cat >expect <<-\EOF &&
 +      --
 +      a{b}c
 +      EOF
 +      test_cmp expect actual
 +'
 +
  test_done
diff --combined t/test-lib.sh
index 9876b4bab0044c73bca5eaccdfa5f6611f3f59b7,25e649c997bbe4dfa03f1eca1f1591181efab5a3..42b1a0aa7f0b8f5b06735293089f640b7f228f62
@@@ -111,6 -111,8 +111,8 @@@ d
                test -z "$HARNESS_ACTIVE" && quiet=t ;;
        --with-dashes)
                with_dashes=t ;;
+       --no-bin-wrappers)
+               no_bin_wrappers=t ;;
        --no-color)
                color= ;;
        --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
                verbose_log=t
                tee=t
                ;;
+       --write-junit-xml)
+               write_junit_xml=t
+               ;;
        --stress)
                stress=t ;;
        --stress=*)
@@@ -622,11 -627,35 +627,35 @@@ trap 'exit $?' INT TERM HU
  # the test_expect_* functions instead.
  
  test_ok_ () {
+       if test -n "$write_junit_xml"
+       then
+               write_junit_xml_testcase "$*"
+       fi
        test_success=$(($test_success + 1))
        say_color "" "ok $test_count - $@"
  }
  
  test_failure_ () {
+       if test -n "$write_junit_xml"
+       then
+               junit_insert="<failure message=\"not ok $test_count -"
+               junit_insert="$junit_insert $(xml_attr_encode "$1")\">"
+               junit_insert="$junit_insert $(xml_attr_encode \
+                       "$(if test -n "$GIT_TEST_TEE_OUTPUT_FILE"
+                          then
+                               test-tool path-utils skip-n-bytes \
+                                       "$GIT_TEST_TEE_OUTPUT_FILE" $GIT_TEST_TEE_OFFSET
+                          else
+                               printf '%s\n' "$@" | sed 1d
+                          fi)")"
+               junit_insert="$junit_insert</failure>"
+               if test -n "$GIT_TEST_TEE_OUTPUT_FILE"
+               then
+                       junit_insert="$junit_insert<system-err>$(xml_attr_encode \
+                               "$(cat "$GIT_TEST_TEE_OUTPUT_FILE")")</system-err>"
+               fi
+               write_junit_xml_testcase "$1" "      $junit_insert"
+       fi
        test_failure=$(($test_failure + 1))
        say_color error "not ok $test_count - $1"
        shift
  }
  
  test_known_broken_ok_ () {
+       if test -n "$write_junit_xml"
+       then
+               write_junit_xml_testcase "$* (breakage fixed)"
+       fi
        test_fixed=$(($test_fixed+1))
        say_color error "ok $test_count - $@ # TODO known breakage vanished"
  }
  
  test_known_broken_failure_ () {
+       if test -n "$write_junit_xml"
+       then
+               write_junit_xml_testcase "$* (known breakage)"
+       fi
        test_broken=$(($test_broken+1))
        say_color warn "not ok $test_count - $@ # TODO known breakage"
  }
@@@ -897,12 -934,21 +934,21 @@@ test_start_ () 
        test_count=$(($test_count+1))
        maybe_setup_verbose
        maybe_setup_valgrind
+       if test -n "$write_junit_xml"
+       then
+               junit_start=$(test-tool date getnanos)
+       fi
  }
  
  test_finish_ () {
        echo >&3 ""
        maybe_teardown_valgrind
        maybe_teardown_verbose
+       if test -n "$GIT_TEST_TEE_OFFSET"
+       then
+               GIT_TEST_TEE_OFFSET=$(test-tool path-utils file-size \
+                       "$GIT_TEST_TEE_OUTPUT_FILE")
+       fi
  }
  
  test_skip () {
  
        case "$to_skip" in
        t)
+               if test -n "$write_junit_xml"
+               then
+                       message="$(xml_attr_encode "$skipped_reason")"
+                       write_junit_xml_testcase "$1" \
+                               "      <skipped message=\"$message\" />"
+               fi
                say_color skip >&3 "skipping test: $@"
                say_color skip "ok $test_count # skip $1 ($skipped_reason)"
                : true
@@@ -949,9 -1002,51 +1002,51 @@@ test_at_end_hook_ () 
        :
  }
  
+ write_junit_xml () {
+       case "$1" in
+       --truncate)
+               >"$junit_xml_path"
+               junit_have_testcase=
+               shift
+               ;;
+       esac
+       printf '%s\n' "$@" >>"$junit_xml_path"
+ }
+ xml_attr_encode () {
+       printf '%s\n' "$@" | test-tool xml-encode
+ }
+ write_junit_xml_testcase () {
+       junit_attrs="name=\"$(xml_attr_encode "$this_test.$test_count $1")\""
+       shift
+       junit_attrs="$junit_attrs classname=\"$this_test\""
+       junit_attrs="$junit_attrs time=\"$(test-tool \
+               date getnanos $junit_start)\""
+       write_junit_xml "$(printf '%s\n' \
+               "    <testcase $junit_attrs>" "$@" "    </testcase>")"
+       junit_have_testcase=t
+ }
  test_done () {
        GIT_EXIT_OK=t
  
+       if test -n "$write_junit_xml" && test -n "$junit_xml_path"
+       then
+               test -n "$junit_have_testcase" || {
+                       junit_start=$(test-tool date getnanos)
+                       write_junit_xml_testcase "all tests skipped"
+               }
+               # adjust the overall time
+               junit_time=$(test-tool date getnanos $junit_suite_start)
+               sed "s/<testsuite [^>]*/& time=\"$junit_time\"/" \
+                       <"$junit_xml_path" >"$junit_xml_path.new"
+               mv "$junit_xml_path.new" "$junit_xml_path"
+               write_junit_xml "  </testsuite>" "</testsuites>"
+       fi
        if test -z "$HARNESS_ACTIVE"
        then
                mkdir -p "$TEST_RESULTS_DIR"
                        error "Tests passed but trash directory already removed before test cleanup; aborting"
  
                        cd "$TRASH_DIRECTORY/.." &&
-                       rm -fr "$TRASH_DIRECTORY" ||
+                       rm -fr "$TRASH_DIRECTORY" || {
+                               # try again in a bit
+                               sleep 5;
+                               rm -fr "$TRASH_DIRECTORY"
+                       } ||
                        error "Tests passed but test cleanup failed; aborting"
                fi
                test_at_end_hook_
        PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$PATH
        GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
  else # normal case, use ../bin-wrappers only unless $with_dashes:
-       git_bin_dir="$GIT_BUILD_DIR/bin-wrappers"
-       if ! test -x "$git_bin_dir/git"
+       if test -n "$no_bin_wrappers"
        then
-               if test -z "$with_dashes"
+               with_dashes=t
+       else
+               git_bin_dir="$GIT_BUILD_DIR/bin-wrappers"
+               if ! test -x "$git_bin_dir/git"
                then
-                       say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
+                       if test -z "$with_dashes"
+                       then
+                               say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH"
+                       fi
+                       with_dashes=t
                fi
-               with_dashes=t
+               PATH="$git_bin_dir:$PATH"
        fi
-       PATH="$git_bin_dir:$PATH"
        GIT_EXEC_PATH=$GIT_BUILD_DIR
        if test -n "$with_dashes"
        then
-               PATH="$GIT_BUILD_DIR:$PATH"
+               PATH="$GIT_BUILD_DIR:$GIT_BUILD_DIR/t/helper:$PATH"
        fi
  fi
  GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
@@@ -1154,7 -1258,7 +1258,7 @@@ test -d "$GIT_BUILD_DIR"/templates/blt 
        error "You haven't built things yet, have you?"
  }
  
 -if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool
 +if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool$X
  then
        echo >&2 'You need to build test-tool:'
        echo >&2 'Run "make t/helper/test-tool" in the source (toplevel) directory'
@@@ -1178,6 -1282,7 +1282,7 @@@ the
  else
        mkdir -p "$TRASH_DIRECTORY"
  fi
  # Use -P to resolve symlinks in our working directory so that the cwd
  # in subprocesses like git equals our $PWD (for pathname comparisons).
  cd -P "$TRASH_DIRECTORY" || exit 1
        test_done
  fi
  
+ if test -n "$write_junit_xml"
+ then
+       junit_xml_dir="$TEST_OUTPUT_DIRECTORY/out"
+       mkdir -p "$junit_xml_dir"
+       junit_xml_base=${0##*/}
+       junit_xml_path="$junit_xml_dir/TEST-${junit_xml_base%.sh}.xml"
+       junit_attrs="name=\"${junit_xml_base%.sh}\""
+       junit_attrs="$junit_attrs timestamp=\"$(TZ=UTC \
+               date +%Y-%m-%dT%H:%M:%S)\""
+       write_junit_xml --truncate "<testsuites>" "  <testsuite $junit_attrs>"
+       junit_suite_start=$(test-tool date getnanos)
+       if test -n "$GIT_TEST_TEE_OUTPUT_FILE"
+       then
+               GIT_TEST_TEE_OFFSET=0
+       fi
+ fi
  # Provide an implementation of the 'yes' utility
  yes () {
        if test $# = 0