diff options
Diffstat (limited to 'gitstatus/install')
-rwxr-xr-x | gitstatus/install | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/gitstatus/install b/gitstatus/install new file mode 100755 index 00000000..126b8339 --- /dev/null +++ b/gitstatus/install @@ -0,0 +1,377 @@ +#!/bin/sh +# +# This script does not have a stable API. + +_gitstatus_install_main() { + if [ -n "${ZSH_VERSION:-}" ]; then + emulate -L sh -o no_unset + else + set -u + fi + + local argv1=$1 + shift + + local no_check= no_install= uname_s= uname_m= gitstatus_dir= dl_status= + local opt= OPTARG= OPTIND=1 + + while getopts ':s:m:d:p:fnh' opt "$@"; do + case "$opt" in + h) + command cat <<\END +Usage: install [-s KERNEL] [-m ARCH] [-d DIR] [-p CMD] [-f|-n] [-- CMD [ARG]...] + +If positional arguments are specified, call this on success: + + CMD [ARG]... DAEMON VERSION INSTALLED + +DAEMON is path to gitstatusd. VERSION is a glob pattern for the +version this daemon should support; it's supposed to be passed as +-G to gitstatusd. INSTALLED is 1 if gitstatusd has just been +downloaded and 0 otherwise. + +Options: + + -s KERNEL use this instead of lowercase `uname -s` + -m ARCH use this instead of lowercase `uname -m` + -d DIR use this instead of `dirname "$0"` + -p CMD eval this every second while downloading gitstatusd + -f download gitstatusd even if there is one locally + -n do not download gitstatusd (fail instead) +END + return + ;; + n) + if [ -n "$no_install" ]; then + >&2 echo "[gitstatus] error: duplicate option: -$opt" + return 1 + fi + no_install=1 + ;; + f) + if [ -n "$no_check" ]; then + >&2 echo "[gitstatus] error: duplicate option: -$opt" + return 1 + fi + no_check=1 + ;; + d) + if [ -n "$gitstatus_dir" ]; then + >&2 echo "[gitstatus] error: duplicate option: -$opt" + return 1 + fi + if [ -z "$OPTARG" ]; then + >&2 echo "[error] incorrect value of -$opt: $OPTARG" + return 1 + fi + gitstatus_dir="$OPTARG" + ;; + p) + if [ -n "$dl_status" ]; then + >&2 echo "[gitstatus] error: duplicate option: -$opt" + return 1 + fi + if [ -z "$OPTARG" ]; then + >&2 echo "[error] incorrect value of -$opt: $OPTARG" + return 1 + fi + dl_status="$OPTARG" + ;; + m) + if [ -n "$uname_m" ]; then + >&2 echo "[gitstatus] error: duplicate option: -$opt" + return 1 + fi + if [ -z "$OPTARG" ]; then + >&2 echo "[error] incorrect value of -$opt: $OPTARG" + return 1 + fi + uname_m="$OPTARG" + ;; + s) + if [ -n "$uname_s" ]; then + >&2 echo "[gitstatus] error: duplicate option: -$opt" + return 1 + fi + if [ -z "$OPTARG" ]; then + >&2 echo "[error] incorrect value of -$opt: $OPTARG" + return 1 + fi + uname_s="$OPTARG" + ;; + \?) >&2 echo "[gitstatus] error: invalid option: -$OPTARG" ; return 1;; + :) >&2 echo "[gitstatus] error: missing required argument: -$OPTARG"; return 1;; + *) >&2 echo "[gitstatus] internal error: unhandled option: -$opt" ; return 1;; + esac + done + + shift "$((OPTIND - 1))" + + : "${gitstatus_dir:=$argv1}" + + if [ -n "$no_check" -a -n "$no_install" ]; then + >&2 echo "[gitstatus] error: incompatible options: -f, -n" + return 1 + fi + + if [ -z "$uname_s" ]; then + uname_s="$(command uname -s)" || return + uname_s="$(printf '%s' "$uname_s" | command tr '[A-Z]' '[a-z]')" || return + fi + if [ -z "$uname_m" ]; then + uname_m="$(command uname -m)" || return + uname_m="$(printf '%s' "$uname_m" | command tr '[A-Z]' '[a-z]')" || return + fi + + local daemon="${GITSTATUS_DAEMON:-}" + local cache_dir="${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}" + + if [ -z "$no_check" ]; then + if [ -n "${daemon##/*}" ]; then + >&2 echo "[gitstatus] error: GITSTATUS_DAEMON is not absolute path: $daemon" + return 1 + fi + if [ -z "$daemon" ]; then + daemon="$gitstatus_dir"/usrbin/gitstatusd + if [ ! -e "$daemon" ]; then + daemon="$daemon"-"$uname_s"-"$uname_m" + if [ ! -e "$daemon" ]; then + daemon= + fi + fi + fi + if [ -n "$daemon" ]; then + local gitstatus_version= libgit2_version= + if ! . "$gitstatus_dir"/build.info; then + >&2 echo "[gitstatus] internal error: failed to source build.info" + return 1 + fi + if [ -z "$gitstatus_version" ]; then + >&2 echo "[gitstatus] internal error: empty gitstatus_version in build.info" + return 1 + fi + [ $# = 0 ] || "$@" "$daemon" "$gitstatus_version" 0 + return + fi + fi + + while IFS= read -r line; do + line="${line###*}" + [ -n "$line" ] || continue + + local uname_s_glob= uname_m_glob= file= version= sha256= + eval "$line" || return + + if [ -z "$uname_s_glob" -o \ + -z "$uname_m_glob" -o \ + -z "$file" -o \ + -z "$version" -o \ + -z "$sha256" ]; then + >&2 echo "[gitstatus] internal error: invalid install.info line: $line" + return 1 + fi + + case "$uname_s" in + $uname_s_glob) ;; + *) continue;; + esac + case "$uname_m" in + $uname_m_glob) ;; + *) continue;; + esac + + # Found a match. The while loop will terminate during this iteration. + + if [ -z "$no_check" ]; then + # Check if a suitable gitstatusd already exists. + local daemon="$cache_dir"/"$file" + if [ -e "$daemon" ]; then + [ $# = 0 ] || "$@" "$daemon" "$version" 0 + return + fi + daemon="$daemon"-"$uname_s"-"$uname_m" + if [ -e "$daemon" ]; then + local gitstatus_version= libgit2_version= + if ! . "$gitstatus_dir"/build.info; then + >&2 echo "[gitstatus] internal error: failed to source build.info" + return 1 + fi + if [ -z "$gitstatus_version" ]; then + >&2 echo "[gitstatus] internal error: empty gitstatus_version in build.info" + return 1 + fi + [ $# = 0 ] || "$@" "$daemon" "$gitstatus_version" 0 + return + fi + fi + + # No suitable gitstatusd exists. Need to download. + + if [ -n "$no_install" ]; then + >&2 echo "[gitstatus] error: no gitstatusd found and installation is disabled" + return 1 + fi + + local daemon="$cache_dir"/"$file" + + if [ -n "${cache_dir##/*}" ]; then + >&2 echo "[gitstatus] error: GITSTATUS_CACHE_DIR is not absolute: $cache_dir" + return 1 + fi + [ -d "$cache_dir" ] || mkdir -p -- "$cache_dir" || return + + local tmpdir + if ! command -v mktemp >/dev/null 2>&1 || + ! tmpdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus-install.XXXXXXXXXX)"; then + tmpdir="${TMPDIR:-/tmp}/gitstatus-install.tmp.$$" + mkdir -p -- "$tmpdir" || return + fi + + ( + if [ -n "${ZSH_VERSION:-}" ]; then + builtin cd -q -- "$tmpdir" || exit + else + cd -- "$tmpdir" || exit + fi + + local fetch + if command -v curl >/dev/null 2>&1; then + fetch="command curl -fsSLo" + elif command -v wget >/dev/null 2>&1; then + fetch="command wget -O" + else + >&2 echo "[gitstatus] error: please install curl or wget" + exit 1 + fi + + local url1="https://github.com/romkatv/gitstatus/releases/download/$version/$file.tar.gz" + local url2="https://gitee.com/romkatv/gitstatus/raw/release-$version/release/$file.tar.gz" + + check_sha256() { + local file="$1".tar.gz + local hash= + if command -v shasum >/dev/null 2>/dev/null; then + hash="$(command shasum -b -a 256 -- "$file")" || hash= + hash="${hash%% *}" + elif command -v sha256sum >/dev/null 2>/dev/null; then + hash="$(command sha256sum -b -- "$file")" || hash= + hash="${hash%% *}" + elif command -v sha256 >/dev/null 2>/dev/null; then + hash="$(command sha256 -- "$file" </dev/null)" || hash= + # Ignore sha256 output if it's from hashalot. It's incompatible. + if [ ${#hash} -lt 64 ]; then + hash= + else + hash="${hash##* }" + fi + fi + [ "$1" = 1 -a -z "$hash" -o "$hash" = "$sha256" ] + } + + local sig='INT QUIT TERM ILL PIPE' + + fetch() { + local trapped= + trap 'trapped=1' $sig + if [ "$1" != 1 ] && command -v sleep >/dev/null 2>/dev/null; then + sleep "$1" + fi + $fetch "$1".tar.gz -- "$2" 2>/dev/null & + local pid=$! + local die="trap - $sig; kill -- $pid 2>/dev/null; exit 1" + trap "$die" $sig + [ -z "$trapped" ] || eval "$die" + wait -- "$pid" 2>/dev/null && check_sha256 "$1" + local ret="$?" + echo -n >"$1".status + trap - $sig + return "$ret" + } + + local trapped= + trap 'trapped=1' $sig + fetch 1 "$url1" & + local pid1=$! + fetch 2 "$url2" & + local pid2=$! + + local die="trap - $sig; kill -- $pid1 $pid2 2>/dev/null; exit 1" + trap "$die" $sig + [ -z "$trapped" ] || eval "$die" + + local n= + while true; do + [ -z "$dl_status" ] || eval "$dl_status" || eval "$die" + if command -v sleep >/dev/null 2>/dev/null; then + command sleep 1 + elif command -v true >/dev/null 2>/dev/null; then + command true + fi + if [ -n "$pid1" -a -e 1.status ]; then + wait -- "$pid1" 2>/dev/null + local ret="$?" + pid1= + if [ "$ret" = 0 ]; then + [ -z "$pid2" ] || kill -- "$pid2" 2>/dev/null + n=1 + break + elif [ -z "$pid2" ]; then + break + fi + elif [ -n "$pid2" -a -e 2.status ]; then + wait -- "$pid2" 2>/dev/null + local ret="$?" + pid2= + if [ "$ret" = 0 ]; then + [ -z "$pid1" ] || kill -- "$pid1" 2>/dev/null + n=2 + break + elif [ -z "$pid1" ]; then + break + fi + fi + done + + trap - $sig + + if [ -z "$n" ]; then + >&2 echo "[gitstatus] error: failed to download gitstatusd from any mirror" + >&2 echo "" + >&2 echo " 1. $url1" + >&2 echo " 2. $url2" + exit 1 + fi + + command tar -xzf "$n".tar.gz || exit + + local tmpfile + if ! command -v mktemp >/dev/null 2>&1 || + ! tmpfile="$(command mktemp "$cache_dir"/gitstatusd.XXXXXXXXXX)"; then + tmpfile="$cache_dir"/gitstatusd.tmp.$$ + fi + + command mv -f -- gitstatusd-* "$tmpfile" || exit + command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit + command rm -f -- "$cache_dir"/"$file" + command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit + command rm -f -- "$tmpfile" + exit 1 + ) + + local ret=$? + command rm -rf -- "$tmpdir" + [ "$ret" = 0 ] || return + + [ $# = 0 ] || "$@" "$daemon" "$version" 1 + return + done <"$gitstatus_dir"/install.info + + >&2 echo "[gitstatus] error: no gitstatusd found for $uname_s $uname_m" + return 1 +} + +if [ -z "${0##*/*}" ]; then + _gitstatus_install_main "${0%/*}" "$@" +else + _gitstatus_install_main . "$@" +fi |