github.com/x-oss-byte/git-lfs@v2.5.2+incompatible/t/testhelpers.sh (about)

     1  #!/usr/bin/env bash
     2  
     3  # assert_pointer confirms that the pointer in the repository for $path in the
     4  # given $ref matches the given $oid and $size.
     5  #
     6  #   $ assert_pointer "master" "path/to/file" "some-oid" 123
     7  assert_pointer() {
     8    local ref="$1"
     9    local path="$2"
    10    local oid="$3"
    11    local size="$4"
    12  
    13    gitblob=$(git ls-tree -lrz "$ref" |
    14      while read -r -d $'\0' x; do
    15        echo $x
    16      done |
    17      grep "$path" | cut -f 3 -d " ")
    18  
    19    actual=$(git cat-file -p $gitblob)
    20    expected=$(pointer $oid $size)
    21  
    22    if [ "$expected" != "$actual" ]; then
    23      exit 1
    24    fi
    25  }
    26  
    27  # refute_pointer confirms that the file in the repository for $path in the
    28  # given $ref is _not_ a pointer.
    29  #
    30  #   $ refute_pointer "master" "path/to/file"
    31  refute_pointer() {
    32    local ref="$1"
    33    local path="$2"
    34  
    35    gitblob=$(git ls-tree -lrz "$ref" |
    36      while read -r -d $'\0' x; do
    37        echo $x
    38      done |
    39      grep "$path" | cut -f 3 -d " ")
    40  
    41    file=$(git cat-file -p $gitblob)
    42    version="version https://git-lfs.github.com/spec/v[0-9]"
    43    oid="oid sha256:[0-9a-f]\{64\}"
    44    size="size [0-9]*"
    45    regex="$version.*$oid.*$size"
    46  
    47    if echo $file | grep -q "$regex"; then
    48      exit 1
    49    fi
    50  }
    51  
    52  # assert_local_object confirms that an object file is stored for the given oid &
    53  # has the correct size
    54  # $ assert_local_object "some-oid" size
    55  assert_local_object() {
    56    local oid="$1"
    57    local size="$2"
    58    local cfg=`git lfs env | grep LocalMediaDir`
    59    local f="${cfg:14}/${oid:0:2}/${oid:2:2}/$oid"
    60    actualsize=$(wc -c <"$f" | tr -d '[[:space:]]')
    61    if [ "$size" != "$actualsize" ]; then
    62      exit 1
    63    fi
    64  }
    65  
    66  # refute_local_object confirms that an object file is NOT stored for an oid.
    67  # If "$size" is given as the second argument, assert that the file exists _and_
    68  # that it does _not_ the expected size
    69  #
    70  # $ refute_local_object "some-oid"
    71  # $ refute_local_object "some-oid" "123"
    72  refute_local_object() {
    73    local oid="$1"
    74    local size="$2"
    75    local cfg=`git lfs env | grep LocalMediaDir`
    76    local regex="LocalMediaDir=(\S+)"
    77    local f="${cfg:14}/${oid:0:2}/${oid:2:2}/$oid"
    78    if [ -e $f ]; then
    79      if [ -z "$size" ]; then
    80        exit 1
    81      fi
    82  
    83      actual_size="$(wc -c < "$f" | awk '{ print $1 }')"
    84      if [ "$size" -eq "$actual_size" ]; then
    85        echo >&2 "fatal: expected object $oid not to have size: $size"
    86        exit 1
    87      fi
    88    fi
    89  }
    90  
    91  # delete_local_object deletes the local storage for an oid
    92  # $ delete_local_object "some-oid"
    93  delete_local_object() {
    94    local oid="$1"
    95    local cfg=`git lfs env | grep LocalMediaDir`
    96    local f="${cfg:14}/${oid:0:2}/${oid:2:2}/$oid"
    97    rm "$f"
    98  }
    99  
   100  # corrupt_local_object corrupts the local storage for an oid
   101  # $ corrupt_local_object "some-oid"
   102  corrupt_local_object() {
   103    local oid="$1"
   104    local cfg=`git lfs env | grep LocalMediaDir`
   105    local f="${cfg:14}/${oid:0:2}/${oid:2:2}/$oid"
   106    cp /dev/null "$f"
   107  }
   108  
   109  
   110  # check that the object does not exist in the git lfs server. HTTP log is
   111  # written to http.log. JSON output is written to http.json.
   112  #
   113  #   $ refute_server_object "reponame" "oid"
   114  refute_server_object() {
   115    local reponame="$1"
   116    local oid="$2"
   117    curl -v "$GITSERVER/$reponame.git/info/lfs/objects/batch" \
   118      -u "user:pass" \
   119      -o http.json \
   120      -d "{\"operation\":\"download\",\"objects\":[{\"oid\":\"$oid\"}]}" \
   121      -H "Accept: application/vnd.git-lfs+json" \
   122      -H "X-Check-Object: 1" \
   123      -H "X-Ignore-Retries: true" 2>&1 |
   124      tee http.log
   125  
   126    [ "0" = "$(grep -c "download" http.json)" ] || {
   127      cat http.json
   128      exit 1
   129    }
   130  }
   131  
   132  # Delete an object on the lfs server. HTTP log is
   133  # written to http.log. JSON output is written to http.json.
   134  #
   135  #   $ delete_server_object "reponame" "oid"
   136  delete_server_object() {
   137    local reponame="$1"
   138    local oid="$2"
   139    curl -v "$GITSERVER/$reponame.git/info/lfs/objects/$oid" \
   140      -X DELETE \
   141      -u "user:pass" \
   142      -o http.json \
   143      -H "Accept: application/vnd.git-lfs+json" 2>&1 |
   144      tee http.log
   145  
   146    grep "200 OK" http.log
   147  }
   148  
   149  # check that the object does exist in the git lfs server. HTTP log is written
   150  # to http.log. JSON output is written to http.json.
   151  assert_server_object() {
   152    local reponame="$1"
   153    local oid="$2"
   154    local refspec="$3"
   155    curl -v "$GITSERVER/$reponame.git/info/lfs/objects/batch" \
   156      -u "user:pass" \
   157      -o http.json \
   158      -d "{\"operation\":\"download\",\"objects\":[{\"oid\":\"$oid\"}],\"ref\":{\"name\":\"$refspec\"}}" \
   159      -H "Accept: application/vnd.git-lfs+json" \
   160      -H "X-Check-Object: 1" \
   161      -H "X-Ignore-Retries: true" 2>&1 |
   162      tee http.log
   163    grep "200 OK" http.log
   164  
   165    grep "download" http.json || {
   166      cat http.json
   167      exit 1
   168    }
   169  }
   170  
   171  # This asserts the lock path and returns the lock ID by parsing the response of
   172  #
   173  #   git lfs lock --json <path>
   174  assert_lock() {
   175    local log="$1"
   176    local path="$2"
   177  
   178    if [ $(grep -c "\"path\":\"$path\"" "$log") -eq 0 ]; then
   179      echo "path '$path' not found in:"
   180      cat "$log"
   181      exit 1
   182    fi
   183  
   184    local jsonid=$(grep -oh "\"id\":\"\w\+\"" "$log")
   185    echo "${jsonid:3}" | tr -d \"\:
   186  }
   187  
   188  # assert that a lock with the given ID exists on the test server
   189  assert_server_lock() {
   190    local reponame="$1"
   191    local id="$2"
   192    local refspec="$3"
   193  
   194    curl -v "$GITSERVER/$reponame.git/info/lfs/locks?refspec=$refspec" \
   195      -u "user:pass" \
   196      -o http.json \
   197      -H "Accept:application/vnd.git-lfs+json" 2>&1 |
   198      tee http.log
   199  
   200    grep "200 OK" http.log
   201    grep "$id" http.json || {
   202      cat http.json
   203      exit 1
   204    }
   205  }
   206  
   207  # refute that a lock with the given ID exists on the test server
   208  refute_server_lock() {
   209    local reponame="$1"
   210    local id="$2"
   211    local refspec="$3"
   212  
   213    curl -v "$GITSERVER/$reponame.git/info/lfs/locks?refspec=$refspec" \
   214      -u "user:pass" \
   215      -o http.json \
   216      -H "Accept:application/vnd.git-lfs+json" 2>&1 | tee http.log
   217  
   218    grep "200 OK" http.log
   219  
   220    [ $(grep -c "$id" http.json) -eq 0 ]
   221  }
   222  
   223  # Assert that .gitattributes contains a given attribute N times
   224  assert_attributes_count() {
   225    local fileext="$1"
   226    local attrib="$2"
   227    local count="$3"
   228  
   229    pattern="\(*.\)\?$fileext\(.*\)$attrib"
   230    actual=$(grep -e "$pattern" .gitattributes | wc -l)
   231    if [ "$(printf "%d" "$actual")" != "$count" ]; then
   232      echo "wrong number of $attrib entries for $fileext"
   233      echo "expected: $count actual: $actual"
   234      cat .gitattributes
   235      exit 1
   236    fi
   237  }
   238  
   239  assert_file_writeable() {
   240    ls -l "$1" | grep -e "^-rw"
   241  }
   242  
   243  refute_file_writeable() {
   244    ls -l "$1" | grep -e "^-r-"
   245  }
   246  
   247  git_root() {
   248    git rev-parse --show-toplevel 2>/dev/null
   249  }
   250  
   251  dot_git_dir() {
   252    echo "$(git_root)/.git"
   253  }
   254  
   255  assert_hooks() {
   256    local git_root="$1"
   257  
   258    if [ -z "$git_root" ]; then
   259      echo >&2 "fatal: (assert_hooks) not in git repository"
   260      exit 1
   261    fi
   262  
   263    [ -x "$git_root/hooks/post-checkout" ]
   264    [ -x "$git_root/hooks/post-commit" ]
   265    [ -x "$git_root/hooks/post-merge" ]
   266    [ -x "$git_root/hooks/pre-push" ]
   267  }
   268  
   269  assert_clean_status() {
   270    status="$(git status)"
   271    echo "$status" | grep "working tree clean" || {
   272      echo $status
   273      git lfs status
   274    }
   275  }
   276  
   277  # pointer returns a string Git LFS pointer file.
   278  #
   279  #   $ pointer abc-some-oid 123 <version>
   280  #   > version ...
   281  pointer() {
   282    local oid=$1
   283    local size=$2
   284    local version=${3:-https://git-lfs.github.com/spec/v1}
   285    printf "version %s
   286  oid sha256:%s
   287  size %s
   288  " "$version" "$oid" "$size"
   289  }
   290  
   291  # wait_for_file simply sleeps until a file exists.
   292  #
   293  #   $ wait_for_file "path/to/upcoming/file"
   294  wait_for_file() {
   295    local filename="$1"
   296    n=0
   297    wait_time=1
   298    while [ $n -lt 17 ]; do
   299      if [ -s $filename ]; then
   300        return 0
   301      fi
   302  
   303      sleep $wait_time
   304      n=`expr $n + 1`
   305      if [ $wait_time -lt 4 ]; then
   306        wait_time=`expr $wait_time \* 2`
   307      fi
   308    done
   309  
   310    echo "$filename did not appear after 60 seconds."
   311    return 1
   312  }
   313  
   314  # setup_remote_repo initializes a bare Git repository that is accessible through
   315  # the test Git server. The `pwd` is set to the repository's directory, in case
   316  # further commands need to be run. This server is running for every test in an
   317  # integration run, so every test file should setup its own remote repository to
   318  # avoid conflicts.
   319  #
   320  #   $ setup_remote_repo "some-name"
   321  #
   322  setup_remote_repo() {
   323    local reponame="$1"
   324    echo "set up remote git repository: $reponame"
   325    repodir="$REMOTEDIR/$reponame.git"
   326    mkdir -p "$repodir"
   327    cd "$repodir"
   328    git init --bare
   329    git config http.receivepack true
   330    git config receive.denyCurrentBranch ignore
   331  }
   332  
   333  # creates a bare remote repository for a local clone. Useful to test pushing to
   334  # a fresh remote server.
   335  #
   336  #   $ setup_alternate_remote "$reponame-whatever"
   337  #   $ setup_alternate_remote "$reponame-whatever" "other-remote-name"
   338  #
   339  setup_alternate_remote() {
   340    local newRemoteName=$1
   341    local remote=${2:-origin}
   342  
   343    wd=`pwd`
   344  
   345    setup_remote_repo "$newRemoteName"
   346    cd $wd
   347    git remote rm "$remote"
   348    git remote add "$remote" "$GITSERVER/$newRemoteName"
   349  }
   350  
   351  # clone_repo clones a repository from the test Git server to the subdirectory
   352  # $dir under $TRASHDIR. setup_remote_repo() needs to be run first. Output is
   353  # written to clone.log.
   354  clone_repo() {
   355    cd "$TRASHDIR"
   356  
   357    local reponame="$1"
   358    local dir="$2"
   359    echo "clone local git repository $reponame to $dir"
   360    out=$(git clone "$GITSERVER/$reponame" "$dir" 2>&1)
   361    cd "$dir"
   362  
   363    git config credential.helper lfstest
   364    echo "$out" > clone.log
   365    echo "$out"
   366  }
   367  
   368  # clone_repo_url clones a Git repository to the subdirectory $dir under $TRASHDIR.
   369  # setup_remote_repo() needs to be run first. Output is written to clone.log.
   370  clone_repo_url() {
   371    cd "$TRASHDIR"
   372  
   373    local repo="$1"
   374    local dir="$2"
   375    echo "clone git repository $repo to $dir"
   376    out=$(git clone "$repo" "$dir" 2>&1)
   377    cd "$dir"
   378  
   379    git config credential.helper lfstest
   380    echo "$out" > clone.log
   381    echo "$out"
   382  }
   383  
   384  # clone_repo_ssl clones a repository from the test Git server to the subdirectory
   385  # $dir under $TRASHDIR, using the SSL endpoint.
   386  # setup_remote_repo() needs to be run first. Output is written to clone_ssl.log.
   387  clone_repo_ssl() {
   388    cd "$TRASHDIR"
   389  
   390    local reponame="$1"
   391    local dir="$2"
   392    echo "clone local git repository $reponame to $dir"
   393    out=$(git clone "$SSLGITSERVER/$reponame" "$dir" 2>&1)
   394    cd "$dir"
   395  
   396    git config credential.helper lfstest
   397  
   398    echo "$out" > clone_ssl.log
   399    echo "$out"
   400  }
   401  
   402  # clone_repo_clientcert clones a repository from the test Git server to the subdirectory
   403  # $dir under $TRASHDIR, using the client cert endpoint.
   404  # setup_remote_repo() needs to be run first. Output is written to clone_client_cert.log.
   405  clone_repo_clientcert() {
   406    cd "$TRASHDIR"
   407  
   408    local reponame="$1"
   409    local dir="$2"
   410    echo "clone $CLIENTCERTGITSERVER/$reponame to $dir"
   411    set +e
   412    out=$(git clone "$CLIENTCERTGITSERVER/$reponame" "$dir" 2>&1)
   413    res="${PIPESTATUS[0]}"
   414    set -e
   415  
   416    if [ "0" -eq "$res" ]; then
   417      cd "$dir"
   418      echo "$out" > clone_client_cert.log
   419  
   420      git config credential.helper lfstest
   421      exit 0
   422    fi
   423  
   424    echo "$out" > clone_client_cert.log
   425    if [ $(grep -c "NSInvalidArgumentException" clone_client_cert.log) -gt 0 ]; then
   426      echo "client-cert-mac-openssl" > clone_client_cert.log
   427      exit 0
   428    fi
   429  
   430    exit 1
   431  }
   432  
   433  # setup_remote_repo_with_file creates a remote repo, clones it locally, commits
   434  # a file tracked by LFS, and pushes it to the remote:
   435  #
   436  #     setup_remote_repo_with_file "reponame" "filename"
   437  setup_remote_repo_with_file() {
   438    local reponame="$1"
   439    local filename="$2"
   440    local dirname="$(dirname "$filename")"
   441  
   442    setup_remote_repo "$reponame"
   443    clone_repo "$reponame" "clone_$reponame"
   444  
   445    mkdir -p "$dirname"
   446  
   447    git lfs track "$filename"
   448    echo "$filename" > "$filename"
   449    git add .gitattributes $filename
   450    git commit -m "add $filename" | tee commit.log
   451  
   452    grep "master (root-commit)" commit.log
   453    grep "2 files changed" commit.log
   454    grep "create mode 100644 $filename" commit.log
   455    grep "create mode 100644 .gitattributes" commit.log
   456  
   457    git push origin master 2>&1 | tee push.log
   458    grep "master -> master" push.log
   459  }
   460  
   461  # substring_position returns the position of a substring in a 1-indexed search
   462  # space.
   463  #
   464  #     [ "$(substring_position "foo bar baz" "baz")" -eq "9" ]
   465  substring_position() {
   466    local str="$1"
   467    local substr="$2"
   468  
   469    # 1) Print the string...
   470    # 2) Remove the substring and everything after it
   471    # 3) Count the number of characters (bytes) left, i.e., the offset of the
   472    #    string we were looking for.
   473  
   474    echo "$str" \
   475      | sed "s/$substr.*$//" \
   476      | wc -c
   477  }
   478  
   479  # repo_endpoint returns the LFS endpoint for a given server and repository.
   480  #
   481  #     [ "$GITSERVER/example/repo.git/info/lfs" = "$(repo_endpoint $GITSERVER example-repo)" ]
   482  repo_endpoint() {
   483    local server="$1"
   484    local repo="$2"
   485  
   486    echo "$server/$repo.git/info/lfs"
   487  }
   488  
   489  # setup initializes the clean, isolated environment for integration tests.
   490  setup() {
   491    cd "$ROOTDIR"
   492  
   493    if [ ! -d "$REMOTEDIR" ]; then
   494      mkdir "$REMOTEDIR"
   495    fi
   496  
   497    echo "# Git LFS: ${LFS_BIN:-$(which git-lfs)}"
   498    git lfs version | sed -e 's/^/# /g'
   499    git version | sed -e 's/^/# /g'
   500  
   501    if [ -z "$GIT_LFS_NO_TEST_COUNT" ]; then
   502      LFSTEST_URL="$LFS_URL_FILE" \
   503      LFSTEST_SSL_URL="$LFS_SSL_URL_FILE" \
   504      LFSTEST_CLIENT_CERT_URL="$LFS_CLIENT_CERT_URL_FILE" \
   505      LFSTEST_DIR="$REMOTEDIR" \
   506      LFSTEST_CERT="$LFS_CERT_FILE" \
   507      LFSTEST_CLIENT_CERT="$LFS_CLIENT_CERT_FILE" \
   508      LFSTEST_CLIENT_KEY="$LFS_CLIENT_KEY_FILE" \
   509        lfstest-count-tests increment
   510    fi
   511  
   512    wait_for_file "$LFS_URL_FILE"
   513    wait_for_file "$LFS_SSL_URL_FILE"
   514    wait_for_file "$LFS_CLIENT_CERT_URL_FILE"
   515    wait_for_file "$LFS_CERT_FILE"
   516    wait_for_file "$LFS_CLIENT_CERT_FILE"
   517    wait_for_file "$LFS_CLIENT_KEY_FILE"
   518  
   519    LFS_CLIENT_CERT_URL=`cat $LFS_CLIENT_CERT_URL_FILE`
   520  
   521    # Set up the initial git config and osx keychain if applicable
   522    HOME="$TESTHOME"
   523    if [ ! -d "$HOME" ]; then
   524      mkdir "$HOME"
   525    fi
   526  
   527    if [ ! -f $HOME/.gitconfig ]; then
   528      git lfs install --skip-repo
   529      git config --global credential.usehttppath true
   530      git config --global credential.helper lfstest
   531      git config --global user.name "Git LFS Tests"
   532      git config --global user.email "git-lfs@example.com"
   533      git config --global http.sslcainfo "$LFS_CERT_FILE"
   534      git config --global http.$LFS_CLIENT_CERT_URL/.sslKey "$LFS_CLIENT_KEY_FILE"
   535      git config --global http.$LFS_CLIENT_CERT_URL/.sslCert "$LFS_CLIENT_CERT_FILE"
   536      git config --global http.$LFS_CLIENT_CERT_URL/.sslVerify "false"
   537    fi | sed -e 's/^/# /g'
   538  
   539    # setup the git credential password storage
   540    mkdir -p "$CREDSDIR"
   541    printf "user:pass" > "$CREDSDIR/127.0.0.1"
   542  
   543    echo "#"
   544    echo "# HOME: $HOME"
   545    echo "# TMP: $TMPDIR"
   546    echo "# CREDS: $CREDSDIR"
   547    echo "# lfstest-gitserver:"
   548    echo "#   LFSTEST_URL=$LFS_URL_FILE"
   549    echo "#   LFSTEST_SSL_URL=$LFS_SSL_URL_FILE"
   550    echo "#   LFSTEST_CLIENT_CERT_URL=$LFS_CLIENT_CERT_URL_FILE ($LFS_CLIENT_CERT_URL)"
   551    echo "#   LFSTEST_CERT=$LFS_CERT_FILE"
   552    echo "#   LFSTEST_CLIENT_CERT=$LFS_CLIENT_CERT_FILE"
   553    echo "#   LFSTEST_CLIENT_KEY=$LFS_CLIENT_KEY_FILE"
   554    echo "#   LFSTEST_DIR=$REMOTEDIR"
   555  }
   556  
   557  # shutdown cleans the $TRASHDIR and shuts the test Git server down.
   558  shutdown() {
   559    # every t/t-*.sh file should cleanup its trashdir
   560    [ -z "$KEEPTRASH" ] && rm -rf "$TRASHDIR"
   561  
   562    if [ -z "$GIT_LFS_NO_TEST_COUNT" ]; then
   563      LFSTEST_DIR="$REMOTEDIR" \
   564      LFS_URL_FILE="$LFS_URL_FILE" \
   565        lfstest-count-tests decrement
   566    fi
   567  }
   568  
   569  tap_show_plan() {
   570    local tests="$1"
   571  
   572    printf "1..%i\n" "$tests"
   573  }
   574  
   575  ensure_git_version_isnt() {
   576    local expectedComparison=$1
   577    local version=$2
   578  
   579    local gitVersion=$(git version | cut -d" " -f3)
   580  
   581    set +e
   582    compare_version $gitVersion $version
   583    result=$?
   584    set -e
   585  
   586    if [[ $result == $expectedComparison ]]; then
   587      echo "skip: $0 (git version $(comparison_to_operator $expectedComparison) $version)"
   588      exit
   589    fi
   590  }
   591  
   592  VERSION_EQUAL=0
   593  VERSION_HIGHER=1
   594  VERSION_LOWER=2
   595  
   596  # Compare $1 and $2 and return VERSION_EQUAL / VERSION_LOWER / VERSION_HIGHER
   597  compare_version() {
   598      if [[ $1 == $2 ]]
   599      then
   600          return $VERSION_EQUAL
   601      fi
   602      local IFS=.
   603      local i ver1=($1) ver2=($2)
   604      # fill empty fields in ver1 with zeros
   605      for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
   606      do
   607          ver1[i]=0
   608      done
   609      for ((i=0; i<${#ver1[@]}; i++))
   610      do
   611          if [[ -z ${ver2[i]} ]]
   612          then
   613              # fill empty fields in ver2 with zeros
   614              ver2[i]=0
   615          fi
   616          if ((10#${ver1[i]} > 10#${ver2[i]}))
   617          then
   618              return $VERSION_HIGHER
   619          fi
   620          if ((10#${ver1[i]} < 10#${ver2[i]}))
   621          then
   622              return $VERSION_LOWER
   623          fi
   624      done
   625      return $VERSION_EQUAL
   626  }
   627  
   628  comparison_to_operator() {
   629    local comparison=$1
   630    if [[ $1 == $VERSION_EQUAL ]]; then
   631      echo "=="
   632    elif [[ $1 == $VERSION_HIGHER ]]; then
   633      echo ">"
   634    elif [[ $1 == $VERSION_LOWER ]]; then
   635      echo "<"
   636    else
   637      echo "???"
   638    fi
   639  }
   640  
   641  # Calculate the object ID from the string passed as the argument
   642  calc_oid() {
   643    printf "$1" | $SHASUM | cut -f 1 -d " "
   644  }
   645  
   646  # Calculate the object ID from the file passed as the argument
   647  calc_oid_file() {
   648    $SHASUM "$1" | cut -f 1 -d " "
   649  }
   650  
   651  # Get a date string with an offset
   652  # Args: One or more date offsets of the form (regex) "[+-]\d+[dmyHM]"
   653  # e.g. +1d = 1 day forward from today
   654  #      -5y = 5 years before today
   655  # Example call:
   656  #   D=$(get_date +1y +1m -5H)
   657  # returns date as string in RFC3339 format ccyy-mm-ddThh:MM:ssZ
   658  # note returns in UTC time not local time hence Z and not +/-
   659  get_date() {
   660    # Wrapped because BSD (inc OSX) & GNU 'date' functions are different
   661    # on Windows under Git Bash it's GNU
   662    if date --version >/dev/null 2>&1 ; then # GNU
   663      ARGS=""
   664      for var in "$@"
   665      do
   666          # GNU offsets are more verbose
   667          unit=${var: -1}
   668          val=${var:0:${#var}-1}
   669          case "$unit" in
   670            d) unit="days" ;;
   671            m) unit="months" ;;
   672            y) unit="years"  ;;
   673            H) unit="hours"  ;;
   674            M) unit="minutes" ;;
   675          esac
   676          ARGS="$ARGS $val $unit"
   677      done
   678      date -d "$ARGS" -u +%Y-%m-%dT%TZ
   679    else # BSD
   680      ARGS=""
   681      for var in "$@"
   682      do
   683          ARGS="$ARGS -v$var"
   684      done
   685      date $ARGS -u +%Y-%m-%dT%TZ
   686    fi
   687  }
   688  
   689  # Convert potentially MinGW bash paths to native Windows paths
   690  # Needed to match generic built paths in test scripts to native paths generated from Go
   691  native_path() {
   692    local arg=$1
   693    if [ $IS_WINDOWS -eq 1 ]; then
   694      # Use params form to avoid interpreting any '\' characters
   695      printf '%s' "$(cygpath -w $arg)"
   696    else
   697      printf '%s' "$arg"
   698    fi
   699  }
   700  
   701  # escape any instance of '\' with '\\' on Windows
   702  escape_path() {
   703    local unescaped="$1"
   704    if [ $IS_WINDOWS -eq 1 ]; then
   705      printf '%s' "${unescaped//\\/\\\\}"
   706    else
   707      printf '%s' "$unescaped"
   708    fi
   709  }
   710  
   711  # As native_path but escape all backslash characters to "\\"
   712  native_path_escaped() {
   713    local unescaped=$(native_path "$1")
   714    escape_path "$unescaped"
   715  }
   716  
   717  # native_path_list_separator prints the operating system-specific path list
   718  # separator.
   719  native_path_list_separator() {
   720    if [ "$IS_WINDOWS" -eq 1 ]; then
   721      printf ";";
   722    else
   723      printf ":";
   724    fi
   725  }
   726  
   727  cat_end() {
   728    if [ $IS_WINDOWS -eq 1 ]; then
   729      printf '^M$'
   730    else
   731      printf '$'
   732    fi
   733  }
   734  
   735  # Compare 2 lists which are newline-delimited in a string, ignoring ordering and blank lines
   736  contains_same_elements() {
   737    # Remove blank lines then sort
   738    diff -u <(printf '%s' "$1" | grep -v '^$' | sort) <(printf '%s' "$2" | grep -v '^$' | sort)
   739  }
   740  
   741  is_stdin_attached() {
   742    test -t0
   743    echo $?
   744  }
   745  
   746  has_test_dir() {
   747    if [ -z "$GIT_LFS_TEST_DIR" ]; then
   748      echo "No GIT_LFS_TEST_DIR. Skipping..."
   749      exit 0
   750    fi
   751  }
   752  
   753  add_symlink() {
   754    local src=$1
   755    local dest=$2
   756  
   757    prefix=`git rev-parse --show-prefix`
   758    hashsrc=`printf "$src" | git hash-object -w --stdin`
   759  
   760    git update-index --add --cacheinfo 120000 "$hashsrc" "$prefix$dest"
   761    git checkout -- "$dest"
   762  }