summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xgit-sonar685
1 files changed, 226 insertions, 459 deletions
diff --git a/git-sonar b/git-sonar
index 1a73949..4da43f8 100755
--- a/git-sonar
+++ b/git-sonar
@@ -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)"