diff options
| -rwxr-xr-x | git-sonar | 685 |
1 files changed, 226 insertions, 459 deletions
@@ -1,498 +1,265 @@ #!/bin/sh -# -# git-sonar -# -# A heads up display for git -GIT_SONAR_VERSION="v0.8.1" - -prepare_colors() { - PRINT_F_OPTION="" - - COLOR_REMOTE_AHEAD="\001${GIT_RADAR_COLOR_REMOTE_AHEAD:-"\\033[1;32m"}\002" - COLOR_REMOTE_BEHIND="\001${GIT_RADAR_COLOR_REMOTE_BEHIND:-"\\033[1;31m"}\002" - COLOR_REMOTE_DIVERGED="\001${GIT_RADAR_COLOR_REMOTE_DIVERGED:-"\\033[1;33m"}\002" - COLOR_REMOTE_NOT_UPSTREAM="\001${GIT_RADAR_COLOR_REMOTE_NOT_UPSTREAM:-"\\033[1;33m"}\002" - - COLOR_LOCAL_AHEAD="\001${GIT_RADAR_COLOR_LOCAL_AHEAD:-"\\033[1;32m"}\002" - COLOR_LOCAL_BEHIND="\001${GIT_RADAR_COLOR_LOCAL_BEHIND:-"\\033[1;31m"}\002" - COLOR_LOCAL_DIVERGED="\001${GIT_RADAR_COLOR_LOCAL_DIVERGED:-"\\033[1;33m"}\002" - - COLOR_CHANGES_STAGED="\001${GIT_RADAR_COLOR_CHANGES_STAGED:-"\\033[1;32m"}\002" - COLOR_CHANGES_UNSTAGED="\001${GIT_RADAR_COLOR_CHANGES_UNSTAGED:-"\\033[1;31m"}\002" - COLOR_CHANGES_CONFLICTED="\001${GIT_RADAR_COLOR_CHANGES_CONFLICTED:-"\\033[1;33m"}\002" - COLOR_CHANGES_UNTRACKED="\001${GIT_RADAR_COLOR_CHANGES_UNTRACKED:-"\\033[1;37m"}\002" - - COLOR_CONDITION="\001${GIT_RADAR_COLOR_CONDITION:-"\\033[1;33m"}\002" - - COLOR_STASH="\001${GIT_RADAR_COLOR_STASH:-"\\033[1;33m"}\002" - - COLOR_BRANCH="\001${GIT_RADAR_COLOR_BRANCH:-"\\033[0m"}\002" - MASTER_SYMBOL="${GIT_RADAR_MASTER_SYMBOL:-""}" # \\x01\\033[0m\\x02\\xF0\\x9D\\x98\\xAE\\x01\\033[0m\\x02 - - PROMPT_FORMAT="${GIT_RADAR_FORMAT:-" \\001\\033[1;30m\\002git:(\\001\\033[0m\\002%{condition: }%{remote: }%{missingups}%{branch}%{ :local}\\001\\033[1;30m\\002)\\001\\033[0m\\002%{ :stash}%{ :changes}"}" - - RESET_COLOR_LOCAL="\001${GIT_RADAR_COLOR_LOCAL_RESET:-"\\033[0m"}\002" - RESET_COLOR_REMOTE="\001${GIT_RADAR_COLOR_REMOTE_RESET:-"\\033[0m"}\002" - RESET_COLOR_CHANGES="\001${GIT_RADAR_COLOR_CHANGES_RESET:-"\\033[0m"}\002" - RESET_COLOR_BRANCH="\001${GIT_RADAR_COLOR_BRANCH_RESET:-"\\033[0m"}\002" - RESET_COLOR_STASH="\001${GIT_RADAR_COLOR_STASH_RESET:-"\\033[0m"}\002" - RESET_COLOR_CONDITION="\001${GIT_RADAR_COLOR_CONDITION_RESET:-"\\033[0m"}\002" +GIT_SONAR_VERSION="v0.9.0-dev" +usage() { + echo "git-sonar [--fetch] [--no-remote-commits] [--no-local-commits]" + echo " [--no-status] [--no-stash]" + echo "" + echo "git-sonar - a heads up display for git" + echo " $GIT_SONAR_VERSION" + echo "" + echo " --fetch" + echo " Periodically fetch from remote repositories asynchronously in" + echo " the background." + echo "" + echo " --no-remote-commits" + echo " Disable showing commit counts between current upstream branch" + echo " and the project default branch." + echo "" + echo " --no-local-commits" + echo " Disable showing commit counts between the current local and" + echo " upstream branches." + echo "" + echo " --no-status" + echo " Disable showing summary of uncommitted changes. Often faster," + echo " but omits a lot of information." + echo "" + echo " --no-stash" + echo " Disable showing git stash count." + echo "" + echo "git-sonar is intended to be invoked by your shell prompt and may be" + echo "configured through the use of various environment variables. See" + echo "the documentation for more details." + exit 0 } -fetch() { - FETCH_TIME="${GIT_RADAR_FETCH_TIME:-"$((5 * 60))"}" - TS_FILE="$(git rev-parse --git-path lastupdatetime 2>/dev/null)" - - now="$(date '+%s')" - timestamp="$(cat "$TS_FILE" 2>/dev/null)" - [ "$timestamp" -eq "$timestamp" ] >/dev/null 2>&1 || timestamp="0" +opt_fetch="" +opt_remote="true" +opt_local="true" +opt_status="true" +opt_stash="true" - if [ "$((now - timestamp))" -ge "$FETCH_TIME" ]; then - nohup git fetch --quiet >/dev/null 2>&1 & - echo "$now" >"$TS_FILE" - fi -} +while true; do + case "$1" in + --fetch) opt_fetch="true" ;; + --no-remote-commits) opt_remote="" ;; + --no-local-commits) opt_local="" ;; + --no-status) opt_status="" ;; + --no-stash) opt_stash="" ;; + --help) usage ;; + -h) usage ;; + *) break + esac + shift +done -commit_short_sha() { - git rev-parse --short HEAD 2>/dev/null -} +if [ $# -ne 0 ]; then + printf 'git-sonar: Unrecognized option given: %s\n' "$1" + exit 1 +fi -branch_name() { - git symbolic-ref --short HEAD 2>/dev/null +# git-precheck will determine whether we are currently inside a git repository, +# as well as whether the repo is in any abnormal state. If outside a repo, exit +# and do not process any remainder of this script. +git-precheck --quiet --ignore-dirty --ignore-untracked >/dev/null 2>&1 +precheck_status=$? +[ "$precheck_status" -ge 4 ] && exit 0 + +# Initialize configuration variables and set default values if not provided by +# the environment. Note: ANSI escape codes (and other unprintable sequences) +# must be enclosed within a \001 byte (ASCII start of heading) at the start and +# a \002 byte (ASCII start of text) at the end - some shells rely on this to +# correctly track the length of their rendered prompt. +COLOR_GRAY="\001\\033[1;30m\002" +COLOR_RED="\001\\033[1;31m\002" +COLOR_GREEN="\001\\033[1;32m\002" +COLOR_YELLOW="\001\\033[1;33m\002" +COLOR_WHITE="\001\\033[1;37m\002" +COLOR_DEF="\001\\033[0m\002" + +FETCH_TIME="${GIT_SONAR_FETCH_TIME:-"300"}" + +ALERT_ICON="${GIT_SONAR_ALERT_ICON:-"${COLOR_YELLOW}⚡"}" +STASH_ICON="${GIT_SONAR_STASH_ICON:-"${COLOR_YELLOW}≡"}" +BRANCH_COLOR="${GIT_SONAR_BRANCH_COLOR:-"$COLOR_DEF"}" +STATUS_SEP="${GIT_SONAR_STATUS_SEPARATOR:-" "}" + +AHEAD_ICON="${GIT_SONAR_AHEAD_ICON:-"${COLOR_GREEN}↑"}" # "←" +BEHIND_ICON="${GIT_SONAR_BEHIND_ICON:-"${COLOR_RED}↓"}" # "→" +DIVERGED_ICON="${GIT_SONAR_DIVERGED_ICON:-"${COLOR_YELLOW}⇵"}" # "⇄" + +STAGED_COLOR="${GIT_SONAR_STAGED_COLOR:-"$COLOR_GREEN"}" +UNSTAGED_COLOR="${GIT_SONAR_UNSTAGED_COLOR:-"$COLOR_RED"}" +UNMERGED_COLOR="${GIT_SONAR_UNMERGED_COLOR:-"$COLOR_YELLOW"}" +UNTRACKED_COLOR="${GIT_SONAR_UNTRACKED_COLOR:-"$COLOR_WHITE"}" + +PROMPT_COLOR="${GIT_SONAR_PROMPT_COLOR:-"$COLOR_GRAY"}" +PROMPT_FORMAT="${GIT_SONAR_PROMPT_FORMAT:-" ${PROMPT_COLOR}git:(${COLOR_DEF}%{alert}%{remote: }%{branch}%{ :local}${PROMPT_COLOR})${COLOR_DEF}%{ :stash}%{ :status}"}" + +# Gather information about the current git branch. +upstream_name="$(git rev-parse --abbrev-ref '@{upstream}' 2>/dev/null)" +branch_name="$(git symbolic-ref --short HEAD 2>/dev/null)" +commit_hash="$(git rev-parse --short HEAD 2>/dev/null)" + +# Helper functions + +determine_default_branch() { + # todo: handle remotes other than 'origin' + for ref in origin/HEAD origin/master origin/main; do + if git rev-parse --verify "$ref" >/dev/null 2>&1; then + echo "$ref" + break + fi + done } -remote_branch_name() { - localRef="$(branch_name)" - remote="$(git config --get "branch.${localRef}.remote")" - if [ -n "$remote" ]; then - remoteBranch="$(git config --get "branch.${localRef}.merge" | sed -e 's/^refs\/heads\///')" - if [ -n "$remoteBranch" ]; then - printf '%s/%s' "$remote" "$remoteBranch" - return 0 - else - return 1 +print_commit_range() { + if [ -n "$1" ] && [ -n "$2" ]; then + ahead="$(git rev-list --count "${1}..${2}" 2>/dev/null)" || ahead="0" + behind="$(git rev-list --count "${2}..${1}" 2>/dev/null)" || behind="0" + + if [ "$behind" -ne 0 ] && [ "$ahead" -ne 0 ]; then + printf '%s%b%b%s' "$behind" "$DIVERGED_ICON" "$COLOR_DEF" "$ahead" + elif [ "$behind" -ne 0 ]; then + printf '%s%b%b' "$behind" "$BEHIND_ICON" "$COLOR_DEF" + elif [ "$ahead" -ne 0 ]; then + printf '%b%b%s' "$AHEAD_ICON" "$COLOR_DEF" "$ahead" + fi fi - else - return 1 - fi -} - -commits_behind_of_remote() { - remote_branch="$1" - [ -n "$remote_branch" ] \ - && git rev-list --count "HEAD..${remote_branch}" 2>/dev/null \ - || printf '0\n' -} - -commits_ahead_of_remote() { - remote_branch="$1" - [ -n "$remote_branch" ] \ - && git rev-list --count "${remote_branch}..HEAD" 2>/dev/null \ - || printf '0\n' -} - -determine_tracked_remote() { - by_branch=$(git config --local branch."$(git rev-parse --abbrev-ref HEAD)".git-radar-tracked-remote) - [ -n "$by_branch" ] && echo "$by_branch" && return 0 - - by_config=$(git config --local git-radar.tracked-remote) - [ -n "$by_config" ] && echo "$by_config" && return 0 - - echo "origin/master" -} - -remote_behind_of_master() { - remote_branch="$1" - tracked_remote="$(determine_tracked_remote)" - [ -n "$remote_branch" ] \ - && [ "$remote_branch" != "$tracked_remote" ] \ - && git rev-list --count "${remote_branch}..${tracked_remote}" 2>/dev/null \ - || printf '0\n' -} - -remote_ahead_of_master() { - remote_branch="$1" - tracked_remote="$(determine_tracked_remote)" - [ -n "$remote_branch" ] \ - && [ "$remote_branch" != "$tracked_remote" ] \ - && git rev-list --count "${tracked_remote}..${remote_branch}" 2>/dev/null \ - || printf '0\n' } -# Diacritic marks for overlaying an arrow over A D C etc -#us="\xE2\x83\x97{$reset_color%}" -#them="\xE2\x83\x96%{$reset_color%}" -#both="\xE2\x83\xA1%{$reset_color%}" +# Prompt elements -porcelain_status() { - git status --porcelain 2>/dev/null -} - -staged_status() { - gitStatus="${1:-"$(porcelain_status)"}" - prefix="${2:-""}" - suffix="${3:-""}" - staged_string="" - - filesModified="$(printf '%s' "$gitStatus" | grep -oE "M[ACDRM ] " | wc -l | grep -oEi '[1-9][0-9]*')" - filesAdded="$(printf '%s' "$gitStatus" | grep -oE "A[MCDR ] " | wc -l | grep -oEi '[1-9][0-9]*')" - filesDeleted="$(printf '%s' "$gitStatus" | grep -oE "D[AMCR ] " | wc -l | grep -oEi '[1-9][0-9]*')" - filesRenamed="$(printf '%s' "$gitStatus" | grep -oE "R[AMCD ] " | wc -l | grep -oEi '[1-9][0-9]*')" - filesCopied="$(printf '%s' "$gitStatus" | grep -oE "C[AMDR ] " | wc -l | grep -oEi '[1-9][0-9]*')" - typeChanged="$(printf '%s' "$gitStatus" | grep -oE "T[AMDR ] " | wc -l | grep -oEi '[1-9][0-9]*')" - - if [ -n "$filesAdded" ]; then - staged_string="$staged_string$filesAdded${prefix}A${suffix}" - fi - if [ -n "$filesDeleted" ]; then - staged_string="$staged_string$filesDeleted${prefix}D${suffix}" - fi - if [ -n "$filesModified" ]; then - staged_string="$staged_string$filesModified${prefix}M${suffix}" - fi - if [ -n "$filesRenamed" ]; then - staged_string="$staged_string$filesRenamed${prefix}R${suffix}" - fi - if [ -n "$filesCopied" ]; then - staged_string="$staged_string$filesCopied${prefix}C${suffix}" - fi - if [ -n "$typeChanged" ]; then - staged_string="$staged_string$typeChanged${prefix}TC${suffix}" - fi - printf '%s' "$staged_string" -} - -conflicted_status() { - gitStatus="${1:-"$(porcelain_status)"}" - prefix="${2:-""}" - suffix="${3:-""}" - conflicted_string="" - - filesUs="$(printf '%s' "$gitStatus" | grep -oE "[AD]U " | wc -l | grep -oEi '[1-9][0-9]*')" - filesThem="$(printf '%s' "$gitStatus" | grep -oE "U[AD] " | wc -l | grep -oEi '[1-9][0-9]*')" - filesBoth="$(printf '%s' "$gitStatus" | grep -oE "(UU|AA|DD) " | wc -l | grep -oEi '[1-9][0-9]*')" - - if [ -n "$filesUs" ]; then - conflicted_string="$conflicted_string$filesUs${prefix}U${suffix}" - fi - if [ -n "$filesThem" ]; then - conflicted_string="$conflicted_string$filesThem${prefix}T${suffix}" - fi - if [ -n "$filesBoth" ]; then - conflicted_string="$conflicted_string$filesBoth${prefix}B${suffix}" - fi - printf '%s' "$conflicted_string" -} - -unstaged_status() { - gitStatus="${1:-"$(porcelain_status)"}" - prefix="${2:-""}" - suffix="${3:-""}" - unstaged_string="" - - filesModified="$(printf '%s' "$gitStatus" | grep -oE "[ACDRM ]M " | wc -l | grep -oEi '[1-9][0-9]*')" - filesDeleted="$(printf '%s' "$gitStatus" | grep -oE "[AMCR ]D " | wc -l | grep -oEi '[1-9][0-9]*')" - typeChanged="$(printf '%s' "$gitStatus" | grep -oE "[AMDR ]T " | wc -l | grep -oEi '[1-9][0-9]*')" - - if [ -n "$filesDeleted" ]; then - unstaged_string="$unstaged_string$filesDeleted${prefix}D${suffix}" - fi - if [ -n "$filesModified" ]; then - unstaged_string="$unstaged_string$filesModified${prefix}M${suffix}" - fi - if [ -n "$typeChanged" ]; then - unstaged_string="$unstaged_string$typeChanged${prefix}TC${suffix}" - fi - printf '%s' "$unstaged_string" +element_alert() { + configured_upstream="$(git config --local "branch.${branch_name}.merge")" + if { [ -n "$configured_upstream" ] && [ -z "$upstream_name" ]; } \ + || [ "$precheck_status" -ge 3 ]; then + printf '%b%b' "$ALERT_ICON" "$COLOR_DEF" + fi } -untracked_status() { - gitStatus="${1:-"$(porcelain_status)"}" - prefix="${2:-""}" - suffix="${3:-""}" - untracked_string="" - - filesUntracked="$(printf '%s' "$gitStatus" | grep "?? " | wc -l | grep -oEi '[1-9][0-9]*')" - - if [ -n "$filesUntracked" ]; then - untracked_string="$untracked_string$filesUntracked${prefix}?${suffix}" - fi - printf '%s' "$untracked_string" +element_branch() { + [ -n "$branch_name" ] && branch="$branch_name" || branch="detached@${commit_hash}" + printf '%b%s%b' "$BRANCH_COLOR" "$branch" "$COLOR_DEF" } -color_changes_status() { - separator="${1:- }" - porcelain="$(porcelain_status)" - changes="" - - if [ -n "$porcelain" ]; then - staged_changes="$(staged_status "$porcelain" "$COLOR_CHANGES_STAGED" "$RESET_COLOR_CHANGES")" - unstaged_changes="$(unstaged_status "$porcelain" "$COLOR_CHANGES_UNSTAGED" "$RESET_COLOR_CHANGES")" - untracked_changes="$(untracked_status "$porcelain" "$COLOR_CHANGES_UNTRACKED" "$RESET_COLOR_CHANGES")" - conflicted_changes="$(conflicted_status "$porcelain" "$COLOR_CHANGES_CONFLICTED" "$RESET_COLOR_CHANGES")" - if [ -n "$staged_changes" ]; then - staged_changes="$separator$staged_changes" +element_remote() { + if [ -n "$opt_remote" ]; then + default_branch="$(determine_default_branch)" + print_commit_range "$default_branch" "$upstream_name" fi - - if [ -n "$unstaged_changes" ]; then - unstaged_changes="$separator$unstaged_changes" - fi - - if [ -n "$conflicted_changes" ]; then - conflicted_changes="$separator$conflicted_changes" - fi - - if [ -n "$untracked_changes" ]; then - untracked_changes="$separator$untracked_changes" - fi - - changes="$staged_changes$conflicted_changes$unstaged_changes$untracked_changes" - fi - printf $PRINT_F_OPTION "${changes#"$separator"}" } -color_local_commits() { - green_ahead_arrow="${COLOR_LOCAL_AHEAD}↑$RESET_COLOR_LOCAL" - red_behind_arrow="${COLOR_LOCAL_BEHIND}↓$RESET_COLOR_LOCAL" - yellow_diverged_arrow="${COLOR_LOCAL_DIVERGED}⇵$RESET_COLOR_LOCAL" - local_commits="" - - if remote_branch="$(remote_branch_name)"; then - local_ahead="$(commits_ahead_of_remote "$remote_branch")" - local_behind="$(commits_behind_of_remote "$remote_branch")" - - if [ "$local_behind" -gt 0 ] && [ "$local_ahead" -gt 0 ]; then - local_commits="$local_behind$yellow_diverged_arrow$local_ahead" - elif [ "$local_behind" -gt 0 ]; then - local_commits="$local_behind$red_behind_arrow" - elif [ "$local_ahead" -gt 0 ]; then - local_commits="$green_ahead_arrow$local_ahead" +element_local() { + if [ -n "$opt_local" ]; then + print_commit_range "$upstream_name" "HEAD" fi - fi - printf $PRINT_F_OPTION "$local_commits" } -color_missing_upstream() { - not_upstream="${COLOR_REMOTE_NOT_UPSTREAM}⚡$RESET_COLOR_REMOTE" - - if remote_branch="$(remote_branch_name)"; then - if ! git rev-parse "$remote_branch" -- >/dev/null 2>&1; then - printf "$not_upstream" +element_stash() { + if [ -n "$opt_stash" ]; then + cnt="$(git rev-list --walk-reflogs --ignore-missing --count refs/stash)" + if [ "$cnt" -ne 0 ]; then + printf '%s%b%b' "$cnt" "$STASH_ICON" "$COLOR_DEF" + fi fi - fi } -color_remote_commits() { - green_ahead_arrow="${COLOR_REMOTE_AHEAD}↑$RESET_COLOR_REMOTE" # "←" - red_behind_arrow="${COLOR_REMOTE_BEHIND}↓$RESET_COLOR_REMOTE" # "→" - yellow_diverged_arrow="${COLOR_REMOTE_DIVERGED}⇵$RESET_COLOR_REMOTE" # "⇄" - remote="" - - if remote_branch="$(remote_branch_name)"; then - remote_ahead="$(remote_ahead_of_master "$remote_branch")" - remote_behind="$(remote_behind_of_master "$remote_branch")" - - if [ "$remote_behind" -gt 0 ] && [ "$remote_ahead" -gt 0 ]; then - remote="$MASTER_SYMBOL$remote_behind$yellow_diverged_arrow$remote_ahead" - elif [ "$remote_ahead" -gt 0 ]; then - remote="$MASTER_SYMBOL$green_ahead_arrow$remote_ahead" - elif [ "$remote_behind" -gt 0 ]; then - remote="$MASTER_SYMBOL$remote_behind$red_behind_arrow" +element_status() { + if [ -n "$opt_status" ]; then + # git status is the most expensive subprocess we call, so place it here + # to bypass it if $opt_status is "false". The output is filtered + # through sed to prevent certain unmerged paths from being double + # counted as staged/unstaged ("AA"/"DD"). + git_status="$(\ + git status --porcelain 2>/dev/null \ + | sed 's/^AA /AU /;s/^DD /DU /' \ + )" + + gs="" + st_sep="$STATUS_SEP" + um_sep="$STATUS_SEP" + us_sep="$STATUS_SEP" + ut_sep="$STATUS_SEP" + + # See "man 1 git-status" sections "Short Format" and "Porcelain Format + # Version 1" for an explanation of these status indicators. + + # Staged + for x in A M R C D T; do + if cnt="$(echo "$git_status" | grep -cE "^${x}[^U] ")"; then + gs="$(printf '%b%b%s%b%s%b' \ + "$gs" "$st_sep" "$cnt" "$STAGED_COLOR" "$x" "$COLOR_DEF" \ + )" + st_sep="" + fi + done + + # Unmerged, conflicted + for x in A U D; do + if cnt="$(echo "$git_status" | grep -cE "^(U${x}|${x}U) ")"; then + gs="$(printf '%b%b%s%b%s%b' \ + "$gs" "$um_sep" "$cnt" "$UNMERGED_COLOR" "$x" "$COLOR_DEF" \ + )" + um_sep="" + fi + done + + # Unstaged + for x in M D T; do # R C omitted + if cnt="$(echo "$git_status" | grep -cE "^[^U]${x} ")"; then + gs="$(printf '%b%b%s%b%s%b' \ + "$gs" "$us_sep" "$cnt" "$UNSTAGED_COLOR" "$x" "$COLOR_DEF" \ + )" + us_sep="" + fi + done + + # Untracked + if cnt="$(echo "$git_status" | grep -cE "^\?\? ")"; then + gs="$(printf '%b%b%s%b?%b' \ + "$gs" "$ut_sep" "$cnt" "$UNTRACKED_COLOR" "$COLOR_DEF" \ + )" + ut_sep="" + fi + + printf '%b' "${gs#"$STATUS_SEP"}" fi - fi - - printf $PRINT_F_OPTION "$remote" } -readable_branch_name() { - printf $PRINT_F_OPTION "$COLOR_BRANCH$(branch_name || printf '%s' "detached@$(commit_short_sha)")$RESET_COLOR_BRANCH" -} +# Main functions -stashed_status() { - printf '%s' "$(git stash list | wc -l 2>/dev/null | grep -oEi '[0-9][0-9]*')" -} +SED_PRE="%{\(\([^%^{^}]*\)\:\)\{0,1\}" +SED_POST="\(\:\([^%^{^}]*\)\)\{0,1\}}" -stash_status() { - number_stashes="$(stashed_status)" - if [ "$number_stashes" -gt 0 ]; then - printf $PRINT_F_OPTION "${number_stashes}${COLOR_STASH}≡${RESET_COLOR_STASH}" - fi -} - -repo_special_condition() { - [ "$precheck_status" -lt 3 ] || printf '%b' "$COLOR_CONDITION!$RESET_COLOR" -} - -render_prompt() { - branch_sed="" - remote_sed="" - local_sed="" - changes_sed="" - stash_sed="" - - - if_pre="%\{([^%{}]{1,}:){0,1}" - if_post="(:[^%{}]{1,}){0,1}\}" - sed_pre="%{\(\([^%^{^}]*\)\:\)\{0,1\}" - sed_post="\(\:\([^%^{^}]*\)\)\{0,1\}}" - - if echo "$PROMPT_FORMAT" | grep -qE "${if_pre}condition${if_post}"; then - condition_result="$(repo_special_condition)" - if [ -n "$condition_result" ]; then - condition_sed="s/${sed_pre}condition${sed_post}/\2${condition_result}\4/" - else - condition_sed="s/${sed_pre}condition${sed_post}//" - fi - fi - if echo "$PROMPT_FORMAT" | grep -qE "${if_pre}missingups${if_post}"; then - missingups_result="$(color_missing_upstream)" - if [ -n "$missingups_result" ]; then - missingups_sed="s/${sed_pre}missingups${sed_post}/\2${missingups_result}\4/" - else - missingups_sed="s/${sed_pre}missingups${sed_post}//" - fi - fi - if echo "$PROMPT_FORMAT" | grep -qE "${if_pre}remote${if_post}"; then - remote_result="$(color_remote_commits)" - if [ -n "$remote_result" ]; then - remote_sed="s/${sed_pre}remote${sed_post}/\2${remote_result}\4/" +prepare_element() { + result="$($2 | sed 's/\//\\\//g')" + if [ -n "$result" ]; then + printf '%b' "s/${SED_PRE}${1}${SED_POST}/\\\\2${result}\\\\4/" else - remote_sed="s/${sed_pre}remote${sed_post}//" + printf '%b' "s/${SED_PRE}${1}${SED_POST}//" fi - fi - if echo "$PROMPT_FORMAT" | grep -qE "${if_pre}branch${if_post}"; then - branch_result="$(readable_branch_name | sed -e 's/\//\\\//g')" - if [ -n "$branch_result" ]; then - branch_sed="s/${sed_pre}branch${sed_post}/\2${branch_result}\4/" - else - branch_sed="s/${sed_pre}branch${sed_post}//" - fi - fi - if echo "$PROMPT_FORMAT" | grep -qE "${if_pre}local${if_post}"; then - local_result="$(color_local_commits)" - if [ -n "$local_result" ]; then - local_sed="s/${sed_pre}local${sed_post}/\2$local_result\4/" - else - local_sed="s/${sed_pre}local${sed_post}//" - fi - fi - if echo "$PROMPT_FORMAT" | grep -qE "${if_pre}changes${if_post}"; then - changes_result="$(color_changes_status)" - if [ -n "$changes_result" ]; then - changes_sed="s/${sed_pre}changes${sed_post}/\2${changes_result}\4/" - else - changes_sed="s/${sed_pre}changes${sed_post}//" - fi - fi - if echo "$PROMPT_FORMAT" | grep -qE "${if_pre}stash${if_post}"; then - stash_result="$(stash_status)" - if [ -n "$stash_result" ]; then - stash_sed="s/${sed_pre}stash${sed_post}/\2${stash_result}\4/" - else - stash_sed="s/${sed_pre}stash${sed_post}//" - fi - fi - - printf '%b' "$PROMPT_FORMAT" | sed \ - -e "$condition_sed" \ - -e "$missingups_sed" \ - -e "$remote_sed" \ - -e "$branch_sed" \ - -e "$changes_sed" \ - -e "$local_sed" \ - -e "$stash_sed" -} - -usage() { - _git="\033[1;30mgit:(\033[0m" - _master="\033[0;37mmaster\033[0m" - _my_branch="\033[0;37mmy-branch\033[0m" - _endgit="\033[1;30m)\033[0m" - _untracked="\033[1;37mA\033[0m" - _added_staged="\033[1;32mA\033[0m" - _modified_unstaged="\033[1;31mM\033[0m" - _local_up="\033[1;32m↑\033[0m" - _2_from_master="\360\235\230\256 2 \033[1;31m→\033[0m " - _diverged_from_master="\360\235\230\256 2 \033[1;33m⇄\033[0m 3 " - _not_upstream="upstream \033[1;31m⚡\033[0m " - _detached="\033[0;37mdetached@94eac67\033[0m" - _conflicted_us="\033[1;33mU\033[0m" - _conflicted_them="\033[1;33mT\033[0m" - _ahead_master="\360\235\230\256 \033[1;32m←\033[0m" - _local_diverged="\033[1;33m⇵\033[0m" - _stash="\033[1;33m≡\033[0m" - echo "git-sonar - a heads up display for git" - echo " $GIT_SONAR_VERSION" - echo "" - echo "examples:" - printf '%b' " $_git$_master$_endgit" - echo " # You are on the master branch and everything is clean" - printf '%b' " $_git$_not_upstream$_my_branch$_endgit" - echo " # Fresh branch that we haven't pushed upstream" - printf '%b' " $_git$_my_branch$_endgit 2$_untracked" - echo " # Two files created that aren't tracked by git" - printf '%b' " $_git$_my_branch$_endgit 1$_added_staged 3$_modified_unstaged" - echo " # 1 new file staged to commit and 3 modifications that we still need to \`git add\`" - printf '%b' " $_git$_2_from_master$_my_branch 3$_local_up$_endgit" - echo " # 3 commits made locally ready to push up while master is ahead of us by 2" - printf '%b' " $_git$_diverged_from_master$_my_branch$_endgit" - echo " # our commits pushed up, master and my-branch have diverged" - printf '%b' " $_git$_detached$_endgit 2${_conflicted_them}3${_conflicted_us}" - echo " # mid rebase, we are detached and have 3 conflicts caused by US and 2 caused by THEM" - printf '%b' " $_git$_diverged_from_master$_my_branch 3${_local_diverged}5$_endgit" - echo " # rebase complete, our rewritten commits now need pushed up" - printf '%b' " $_git$_ahead_master 3 $_my_branch$_endgit" - echo " # origin/my-branch is up to date with master and has our 3 commits waiting merge" - printf '%b' " $_git$_master$_endgit 3$_stash" - echo " # You have 3 stashes stored" - - echo "" - echo "usage:" - echo " git-sonar [-h|--help] [-f|--fetch] [--zsh|--bash|--fish]" - echo "" - echo " -h --help # Display this text" - echo " -f --fetch # Periodically fetch your repo asynchronously in the background" - echo " --zsh # (does nothing - for compatibility with git-radar)" - echo " --bash # (does nothing - for compatibility with git-radar)" - echo " --fish # (does nothing - for compatibility with git-radar)" - exit } -do_fetch="" +# If fetch was requested, manage timestamp and run fetch if necessary +if [ -n "$opt_fetch" ]; then + tsfile="$(git rev-parse --git-path lastupdatetime 2>/dev/null)" + now="$(date '+%s')" + timestamp="$(cat "$tsfile" 2>/dev/null)" + [ "$timestamp" -eq "$timestamp" ] >/dev/null 2>&1 || timestamp="0" -while true; do - case "$1" in - --help) usage ;; - -h) usage ;; - --fetch) do_fetch="true" ;; - -f) do_fetch="true" ;; - --zsh) ;; - --bash) ;; - --fish) ;; - *) break - esac - shift -done - -if [ $# -ne 0 ]; then - printf 'sonar: Unrecognized option given: %s\n' "$1" - exit 1 + if [ "$((now - timestamp))" -ge "$FETCH_TIME" ]; then + nohup git fetch --quiet >/dev/null 2>&1 & + echo "$now" >"$tsfile" + fi fi -git-precheck --quiet --ignore-dirty --ignore-untracked >/dev/null 2>&1 -precheck_status=$? - -# Guard all active operations by this "is in repo" check -if [ "$precheck_status" -lt 4 ]; then - # Merge configuration from accepted RC files - [ -f "$HOME/.gitradarrc" ] && . "$HOME/.gitradarrc" - [ -f "$HOME/.gitradarrc.bash" ] && . "$HOME/.gitradarrc.bash" - [ -f "$HOME/.gitradarrc.zsh" ] && . "$HOME/.gitradarrc.zsh" - [ -f "$HOME/.gitsonarrc" ] && . "$HOME/.gitsonarrc" - - [ -n "$do_fetch" ] && fetch >/dev/null 2>&1 - prepare_colors - render_prompt -fi +# Render prompt elements from format string +printf '%b' "$PROMPT_FORMAT" | sed \ + -e "$(prepare_element alert element_alert)" \ + -e "$(prepare_element branch element_branch)" \ + -e "$(prepare_element remote element_remote)" \ + -e "$(prepare_element local element_local)" \ + -e "$(prepare_element stash element_stash)" \ + -e "$(prepare_element status element_status)" |
