diff --git a/gitstatus.plugin.sh b/gitstatus.plugin.sh index e0cd206d..61b81e6a 100644 --- a/gitstatus.plugin.sh +++ b/gitstatus.plugin.sh @@ -36,6 +36,17 @@ # -D Unless this option is specified, report zero staged, unstaged and conflicted # changes for repositories with bash.showDirtyState = false. function gitstatus_start() { + if [[ "$BASH_VERSION" < 4 ]]; then + >&2 printf 'gitstatus_start: need bash version >= 4.0, found %s\n' "$BASH_VERSION" + >&2 printf '\n' + >&2 printf 'To see the version of the current shell, type:\n' + >&2 printf '\n' + >&2 printf ' \033[32mecho\033[0m \033[33m"$BASH_VERSION"\033[0m\n' + >&2 printf '\n' + >&2 printf 'The output of `\033[32mbash\033[0m --version` may be different and is not relevant.\n' + return 1 + fi + unset OPTIND local opt timeout=5 max_dirty=-1 extra_flags local max_num_staged=1 max_num_unstaged=1 max_num_conflicted=1 max_num_untracked=1 @@ -69,7 +80,7 @@ function gitstatus_start() { local gitstatus_plugin_dir="$PWD" fi - local tmpdir req_fifo resp_fifo + local tmpdir req_fifo resp_fifo culprit function gitstatus_start_impl() { local log_level="${GITSTATUS_LOG_LEVEL:-}" @@ -137,7 +148,7 @@ function gitstatus_start() { } set -- -d "$gitstatus_plugin_dir" -s "$uname_s" -m "$uname_m" \ - -p "printf '.\036' >&$fd_out" -- _gitstatus_set_daemon + -p "printf '.\036' >&$fd_out" -e "$fd_out" -- _gitstatus_set_daemon [[ "${GITSTATUS_AUTO_INSTALL:-1}" -ne 0 ]] || set -- -n "$@" source "$gitstatus_plugin_dir"/install || return [[ -n "$_gitstatus_bash_daemon" ]] || return @@ -166,7 +177,7 @@ function gitstatus_start() { trap - ${sig[@]} case "$ret" in 0|129|130|131|137|141|143|159) - echo -nE $'bye\x1f0\x1e' >&"$fd_out" + echo -nE $'}bye\x1f0\x1e' >&"$fd_out" exit "$ret" ;; esac @@ -192,7 +203,7 @@ function gitstatus_start() { trap "trap - ${sig[*]}; kill $pid &>/dev/null" ${sig[@]} wait "$pid" trap - ${sig[@]} - echo -nE $'bye\x1f0\x1e' >&"$fd_out" + echo -nE $'}bye\x1f0\x1e' >&"$fd_out" ) & disown ) & disown } 0"$GITSTATUS_DAEMON_LOG" @@ -205,12 +216,15 @@ function gitstatus_start() { [[ "$GITSTATUS_DAEMON_PID" == [1-9]* ]] || return local reply - echo -nE $'hello\x1f\x1e' >&$_GITSTATUS_REQ_FD || return + echo -nE $'}hello\x1f\x1e' >&$_GITSTATUS_REQ_FD || return local dl= while true; do - IFS='' read -rd $'\x1e' -u $_GITSTATUS_RESP_FD -t "$timeout" reply || return - [[ "$reply" == $'hello\x1f0' ]] && break - [[ "$reply" == . ]] || return + reply= + if ! IFS='' read -rd $'\x1e' -u $_GITSTATUS_RESP_FD -t "$timeout" reply; then + culprit="$reply" + return 1 + fi + [[ "$reply" == $'}hello\x1f0' ]] && break if [[ -z "$dl" ]]; then dl=1 if [[ -t 2 ]]; then @@ -238,8 +252,11 @@ function gitstatus_start() { } if ! gitstatus_start_impl; then - echo "" >&2 - echo "gitstatus_start: failed to start gitstatusd" >&2 + >&2 printf '\n' + >&2 printf '[\033[31mERROR\033[0m]: gitstatus failed to initialize.\n' + if [[ -n "${culprit-}" ]]; then + >&2 printf '\n%s\n' "$culprit" + fi [[ -z "${req_fifo:-}" ]] || command rm -f "$req_fifo" [[ -z "${resp_fifo:-}" ]] || command rm -f "$resp_fifo" unset -f gitstatus_start_impl diff --git a/gitstatus.plugin.zsh b/gitstatus.plugin.zsh index 603e04d0..ca0fb311 100644 --- a/gitstatus.plugin.zsh +++ b/gitstatus.plugin.zsh @@ -404,8 +404,8 @@ function _gitstatus_daemon"${1:-}"() { local gitstatus_plugin_dir_var=_gitstatus_plugin_dir$fsuf local gitstatus_plugin_dir=${(P)gitstatus_plugin_dir_var} - builtin set -- -d $gitstatus_plugin_dir -s $uname_s -m $uname_m -p "printf . >&$pipe_fd" -- \ - _gitstatus_set_daemon$fsuf + builtin set -- -d $gitstatus_plugin_dir -s $uname_s -m $uname_m \ + -p "printf '\\001' >&$pipe_fd" -e $pipe_fd -- _gitstatus_set_daemon$fsuf [[ ${GITSTATUS_AUTO_INSTALL:-1} == (|-|+)<1-> ]] || builtin set -- -n "$@" builtin source $gitstatus_plugin_dir/install || return [[ -n $_gitstatus_zsh_daemon ]] || return @@ -542,7 +542,7 @@ function gitstatus_start"${1:-}"() { fi local -i lock_fd resp_fd stderr_fd - local file_prefix xtrace=/dev/null daemon_log=/dev/null + local file_prefix xtrace=/dev/null daemon_log=/dev/null culprit { if (( _GITSTATUS_STATE_$name )); then @@ -637,8 +637,8 @@ function gitstatus_start"${1:-}"() { [[ $req_fd == <1-> ]] || return typeset -gi _GITSTATUS_REQ_FD_$name=req_fd - print -nru $req_fd -- $'hello\x1f\x1e' || return - local expected=$'hello\x1f0\x1e' actual + print -nru $req_fd -- $'}hello\x1f\x1e' || return + local expected=$'}hello\x1f0\x1e' actual if (( $+functions[p10k] )) && [[ ! -t 1 && ! -t 0 ]]; then local -F deadline='EPOCHREALTIME + 4' else @@ -647,8 +647,15 @@ function gitstatus_start"${1:-}"() { while true; do [[ -t $resp_fd ]] sysread -s 1 -t $timeout -i $resp_fd actual || return - [[ $actual == h ]] && break - [[ $actual == . ]] || return + [[ $expected == $actual* ]] && break + if [[ $actual != $'\1' ]]; then + [[ -t $resp_fd ]] + while sysread -t $timeout -i $resp_fd 'actual[$#actual+1]'; do + [[ -t $resp_fd ]] + done + culprit=$actual + return 1 + fi (( EPOCHREALTIME < deadline )) && continue if (( deadline > 0 )); then deadline=0 @@ -747,7 +754,10 @@ function gitstatus_start"${1:-}"() { print -ru2 -- '' print -Pru2 -- '[%F{red}ERROR%f]: gitstatus failed to initialize.' print -ru2 -- '' - print -ru2 -- ' Your Git prompt may disappear or become slow.' + if [[ -n $culprit ]]; then + print -ru2 -- $culprit + return err + fi if [[ -s $xtrace ]]; then print -ru2 -- '' print -Pru2 -- " Zsh log (%U${xtrace//\%/%%}%u):" diff --git a/install b/install index 91a47205..0c103b8a 100755 --- a/install +++ b/install @@ -2,6 +2,45 @@ # # This script does not have a stable API. +_gitstatus_install_daemon_found() { + local installed="$1" + shift + + case "$daemon" in + *-darwin-x86_64);; + *) + [ $# = 0 ] || "$@" "$daemon" "$version" "$installed" + return + ;; + esac + + local cpu + if [ "$uname_sm" != 'darwin arm64' ] || + [ -e /Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist ] || + [ ! -x /usr/sbin/softwareupdate ] || + ! cpu="$(/usr/sbin/sysctl -n machdep.cpu.brand_string)"; then + [ $# = 0 ] || "$@" "$daemon" "$version" "$installed" + return + fi + + case "$cpu" in + *Intel*);; + *) + [ $# = 0 ] || "$@" "$daemon" "$version" "$installed" + return + ;; + esac + + >&"$e" printf 'Please run the following command to install Rosetta:\n' + >&"$e" printf '\n' + >&"$e" printf ' \033[32m/usr/sbin/softwareupdate\033[0m --install-rosetta\n' + >&"$e" printf '\n' + >&"$e" printf 'See for details: \033[4mhttps://support.apple.com/en-us/HT211861\033[0m\n' + >&"$e" printf '\n' + >&"$e" printf 'Once Rosetta is installed, restart your shell.\n' + return 1 +} + _gitstatus_install_main() { if [ -n "${ZSH_VERSION:-}" ]; then emulate -L sh -o no_unset @@ -12,14 +51,14 @@ _gitstatus_install_main() { local argv1="$1" shift - local no_check= no_install= uname_s= uname_m= gitstatus_dir= dl_status= + local no_check= no_install= uname_s= uname_m= gitstatus_dir= dl_status= e= local opt= OPTARG= OPTIND=1 - while getopts ':s:m:d:p:fnh' opt "$@"; do + while getopts ':s:m:d:p:e:fnh' opt "$@"; do case "$opt" in h) command cat <<\END -Usage: install [-s KERNEL] [-m ARCH] [-d DIR] [-p CMD] [-f|-n] [-- CMD [ARG]...] +Usage: install [-s KERNEL] [-m ARCH] [-d DIR] [-p CMD] [-e ERRFD] [-f|-n] [-- CMD [ARG]...] If positional arguments are specified, call this on success: @@ -36,6 +75,7 @@ Options: -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 + -e ERRFD write error messages to this file descriptor -f download gitstatusd even if there is one locally -n do not download gitstatusd (fail instead) END @@ -77,6 +117,17 @@ END fi dl_status="$OPTARG" ;; + e) + if [ -n "$e" ]; 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 + e="$OPTARG" + ;; m) if [ -n "$uname_m" ]; then >&2 echo "[gitstatus] error: duplicate option: -$opt" @@ -107,6 +158,7 @@ END shift "$((OPTIND - 1))" + : "${e:=2}" : "${gitstatus_dir:=$argv1}" if [ -n "$no_check" -a -n "$no_install" ]; then @@ -144,7 +196,8 @@ END >&2 echo "[gitstatus] internal error: empty gitstatus_version in build.info" return 1 fi - [ $# = 0 ] || "$@" "$daemon" "$gitstatus_version" 0 + local version="$gitstatus_version" + _gitstatus_install_daemon_found 0 "$@" return fi fi @@ -184,7 +237,7 @@ END [ -e "$daemon" ] || daemon= fi if [ -n "$daemon" ]; then - [ $# = 0 ] || "$@" "$daemon" "$version" 0 + _gitstatus_install_daemon_found 0 "$@" return fi fi @@ -202,17 +255,59 @@ END >&2 echo "[gitstatus] error: GITSTATUS_CACHE_DIR is not absolute: $cache_dir" return 1 fi - [ -d "$cache_dir" ] || mkdir -p -- "$cache_dir" || return + if [ ! -d "$cache_dir" ] && ! mkdir -p -- "$cache_dir" || [ ! -w "$cache_dir" ]; then + local dir="$cache_dir" + while true; do + if [ -e "$dir" ]; then + if [ ! -d "$dir" ]; then + >&"$e" printf 'Not a directory: \033[4;31m%s\033[0m\n' "$dir" + >&"$e" printf '\n' + >&"$e" printf 'Delete it, then restart your shell.\n' + elif [ ! -w "$dir" ]; then + >&"$e" printf 'Directory is not writable: \033[4;31m%s\033[0m\n' "$dir" + >&"$e" printf '\n' + >&"$e" printf 'Make it writable, then restart your shell.\n' + fi + break + fi + if [ "$dir" = / ] || [ "$dir" = . ]; then + break + fi + dir="$(dirname -- "$dir")" + done + return 1 + fi local tmpdir if ! command -v mktemp >/dev/null 2>&1 || - ! tmpdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus-install.XXXXXXXXXX)"; then + ! tmpdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus-install.XXXXXXXXXX)"; then tmpdir="${TMPDIR:-/tmp}/gitstatus-install.tmp.$$" - mkdir -p -- "$tmpdir" || return + if ! mkdir -p -- "$tmpdir"; then + local dir="${TMPDIR:-/tmp}" + if [ -z "${TMPDIR:-}" ]; then + local label='directory' + else + local label='directory (\033[1mTMPDIR\033[m)' + fi + if [ ! -e "$dir" ]; then + >&"$e" printf 'Temporary '"$label"' does not exist: \033[4;31m%s\033[0m\n' "$dir" + >&"$e" printf '\n' + >&"$e" printf 'Create it, then restart your shell.\n' + elif [ ! -d "$dir" ]; then + >&"$e" printf 'Not a '"$label"': \033[4;31m%s\033[0m\n' "$dir" + >&"$e" printf '\n' + >&"$e" printf 'Make it a directory, then restart your shell.\n' + elif [ ! -w "$dir" ]; then + >&"$e" printf 'Temporary '"$label"' is not writable: \033[4;31m%s\033[0m\n' "$dir" + >&"$e" printf '\n' + >&"$e" printf 'Make it writable, then restart your shell.\n' + fi + return 1 + fi fi if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then - >&2 echo "[gitstatus] error: please install curl or wget" + >&"$e" printf 'Please install \033[32mcurl\033[0m or \033[32mwget\033[0m, then restart your shell.\n' return 1 fi @@ -363,10 +458,12 @@ END 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" + >&"$e" printf 'Failed to download \033[32m%s\033[0m from any mirror:\n' "$file" + >&"$e" printf '\n' + >&"$e" printf ' 1. \033[4m%s\033[0m\n' "$url1" + >&"$e" printf ' 2. \033[4m%s\033[0m\n' "$url2" + >&"$e" printf '\n' + >&"$e" printf 'Check your internet connection, then restart your shell.\n' exit 1 fi @@ -378,7 +475,7 @@ END tmpfile="$cache_dir"/gitstatusd.tmp.$$ fi - command mv -f -- "$tmpdir"/gitstatusd-* "$tmpfile" || exit + command mv -f -- "$tmpdir"/"$file" "$tmpfile" || exit command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit command rm -f -- "$cache_dir"/"$file" command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit @@ -390,13 +487,13 @@ END command rm -rf -- "$tmpdir" [ "$ret" = 0 ] || return - [ $# = 0 ] || "$@" "$daemon" "$version" 1 + _gitstatus_install_daemon_found 1 "$@" return done <"$gitstatus_dir"/install.info - >&2 echo "[gitstatus] error: no gitstatusd found for $uname_s $uname_m" - >&2 echo "" - >&2 echo "See: https://github.com/romkatv/gitstatus/blob/master/README.md#compiling" + >&"$e" printf 'There is no prebuilt \033[32mgitstatusd\033[0m for \033[1m%s\033[0m.\n' "$uname_s $uname_m" + >&"$e" printf '\n' + >&"$e" printf 'See: \033[4mhttps://github.com/romkatv/gitstatus#compiling\033[0m\n' return 1 }