summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Hunter <m@lfurio.us>2026-04-19 18:49:35 -0400
committerMatt Hunter <m@lfurio.us>2026-05-06 05:49:52 -0400
commitcee3f0443ff992a8f64f13d66da4867d1db836de (patch)
treeb85541edb58b9c1bfb37e52fdbe808ad8f1379da
parent89c100d72f39afff26f07b7c5ef57ce9fc9504b1 (diff)
downloadgit-sonar-cee3f0443ff992a8f64f13d66da4867d1db836de.tar.gz
git-sonar-cee3f0443ff992a8f64f13d66da4867d1db836de.zip
Bulk rewrite of git-sonar scriptHEADmaster
Recent changes to git-sonar have made a top-to-bottom redesign and reimplementation more approachable. This patch is a full rewrite of git-sonar, with a focus on simplicity and further breaking ties with legacy git-radar stuff. A high-level itemization of changes is below: - Removed shell selector options - Removed '-f' short option (use '--fetch') - Removed support for rc configuration files - Removed remote commits "master symbol" - Removed context-specific color reset codes (now always \e[0m) - Removed support for git-config keys (determine_default_branch) This is now determined automatically. - Added feature opt-out options - Added PROMPT_COLOR configuration variable - Configurable icons/colors are globally merged into single variables (the user must insert \001 and \002) - Configuration variables renamed from GIT_RADAR_ to GIT_SONAR_ - Most configuration variables are renamed - Configuration for remote and local commits are merged together - Terminology "changes conflicted" is now "changes unmerged" - %{changes} formatter is replaced with individual stage elements - Unmerged status now shows A/D instead of us-vs-them Signed-off-by: Matt Hunter <m@lfurio.us>
-rwxr-xr-xgit-sonar653
1 files changed, 216 insertions, 437 deletions
diff --git a/git-sonar b/git-sonar
index 25772e8..4978818 100755
--- a/git-sonar
+++ b/git-sonar
@@ -1,481 +1,260 @@
#!/bin/sh
-#
-# git-sonar
-#
-# A heads up display for git
-GIT_SONAR_VERSION="v0.8.1"
+GIT_SONAR_VERSION="v0.9.0-dev"
-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_ALERT="\001${GIT_RADAR_COLOR_ALERT:-"\\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%{alert}%{remote: }%{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"
-}
-
-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"
-
- if [ "$((now - timestamp))" -ge "$FETCH_TIME" ]; then
- nohup git fetch --quiet >/dev/null 2>&1 &
- echo "$now" >"$TS_FILE"
- fi
-}
-
-commit_short_sha() {
- git rev-parse --short HEAD 2>/dev/null
-}
-
-branch_name() {
- git symbolic-ref --short HEAD 2>/dev/null
-}
-
-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
- 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'
+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
}
-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
+opt_fetch=""
+opt_remote="true"
+opt_local="true"
+opt_status="true"
+opt_stash="true"
- by_config=$(git config --local git-radar.tracked-remote)
- [ -n "$by_config" ] && echo "$by_config" && return 0
+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
- echo "origin/master"
-}
+if [ $# -ne 0 ]; then
+ printf 'git-sonar: Unrecognized option given: %s\n' "$1"
+ exit 1
+fi
-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'
+# 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_DEFAULT="\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_DEFAULT"}"
+
+COMMIT_ICON_AHEAD="${GIT_SONAR_COMMIT_ICON_AHEAD:-"${COLOR_GREEN}↑"}" # "←"
+COMMIT_ICON_BEHIND="${GIT_SONAR_COMMIT_ICON_BEHIND:-"${COLOR_RED}↓"}" # "→"
+COMMIT_ICON_DIVERGED="${GIT_SONAR_COMMIT_ICON_DIVERGED:-"${COLOR_YELLOW}⇵"}" # "⇄"
+
+STATUS_COLOR_STAGED="${GIT_SONAR_STATUS_COLOR_STAGED:-"$COLOR_GREEN"}"
+STATUS_COLOR_UNSTAGED="${GIT_SONAR_STATUS_COLOR_UNSTAGED:-"$COLOR_RED"}"
+STATUS_COLOR_UNMERGED="${GIT_SONAR_STATUS_COLOR_UNMERGED:-"$COLOR_YELLOW"}"
+STATUS_COLOR_UNTRACKED="${GIT_SONAR_STATUS_COLOR_UNTRACKED:-"$COLOR_WHITE"}"
+
+PROMPT_COLOR="${GIT_SONAR_PROMPT_COLOR:-"$COLOR_GRAY"}"
+PROMPT_FORMAT="${GIT_SONAR_PROMPT_FORMAT:-" ${PROMPT_COLOR}git:(${COLOR_DEFAULT}%{alert}%{remote: }%{branch}%{ :local}${PROMPT_COLOR})${COLOR_DEFAULT}%{ :stash}%{ :staged}%{ :unmerged}%{ :unstaged}%{ :untracked}"}"
+
+# Gather current git status and branch information. git porcelain status can
+# be especially expensive to compute, so bypass it if the prompt disables status
+# information.
+[ -n "$opt_status" ] && git_status="$(git status --porcelain 2>/dev/null)" || git_status=""
+upstream_name="$(git rev-parse --abbrev-ref --symbolic-full-name '@{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_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'
+line_count() {
+ # macos 'wc' produces odd formatting (extra spaces)
+ # The grep is present to deal with this, filter out cases where the count
+ # is zero, and provide a return value (true if count is non-zero).
+ wc -l 2>/dev/null | grep -oE '[1-9][0-9]*'
}
-# 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%}"
-
-porcelain_status() {
- git status --porcelain 2>/dev/null
+status_count() {
+ echo "$git_status" | grep -E "$1" | line_count
}
-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"
+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" "$COMMIT_ICON_DIVERGED" "$COLOR_DEFAULT" "$ahead"
+ elif [ "$behind" -ne 0 ]; then
+ printf '%s%b%b' "$behind" "$COMMIT_ICON_BEHIND" "$COLOR_DEFAULT"
+ elif [ "$ahead" -ne 0 ]; then
+ printf '%b%b%s' "$COMMIT_ICON_AHEAD" "$COLOR_DEFAULT" "$ahead"
+ fi
+ fi
}
-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"
-}
+# Prompt elements
-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_DEFAULT"
+ 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_DEFAULT"
}
-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_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_stash() {
+ if [ -n "$opt_stash" ]; then
+ if cnt="$(git stash list | line_count)"; then
+ printf '%s%b%b' "$cnt" "$STASH_ICON" "$COLOR_DEFAULT"
+ fi
fi
- fi
-
- printf $PRINT_F_OPTION "$remote"
-}
-
-color_alert() {
- if { remote_branch="$(remote_branch_name)" \
- && ! git rev-parse "$remote_branch" -- >/dev/null 2>&1; } \
- || [ "$precheck_status" -ge 3 ]; then
- printf '%b' "${COLOR_ALERT}⚡${RESET_COLOR}"
- fi
}
-readable_branch_name() {
- printf $PRINT_F_OPTION "$COLOR_BRANCH$(branch_name || printf '%s' "detached@$(commit_short_sha)")$RESET_COLOR_BRANCH"
+element_staged() {
+ if [ -n "$opt_status" ]; then
+ for x in M T A D R C; do
+ if cnt="$(status_count "^${x}[MTDRC ] ")"; then
+ printf '%s%b%s%b' "$cnt" "$STATUS_COLOR_STAGED" "$x" "$COLOR_DEFAULT"
+ fi
+ done
+ fi
}
-stashed_status() {
- printf '%s' "$(git stash list | wc -l 2>/dev/null | grep -oEi '[0-9][0-9]*')"
+element_unstaged() {
+ if [ -n "$opt_status" ]; then
+ for x in M T D R C; do
+ if cnt="$(status_count "^[MTADRC ]${x} ")"; then
+ printf '%s%b%s%b' "$cnt" "$STATUS_COLOR_UNSTAGED" "$x" "$COLOR_DEFAULT"
+ fi
+ done
+ fi
}
-stash_status() {
- number_stashes="$(stashed_status)"
- if [ "$number_stashes" -gt 0 ]; then
- printf $PRINT_F_OPTION "${number_stashes}${COLOR_STASH}≡${RESET_COLOR_STASH}"
- fi
+element_unmerged() {
+ if [ -n "$opt_status" ]; then
+ for x in A D U; do
+ if cnt="$(status_count "^(${x}${x}|U${x}|${x}U) ")"; then
+ printf '%s%b%s%b' "$cnt" "$STATUS_COLOR_UNMERGED" "$x" "$COLOR_DEFAULT"
+ fi
+ done
+ fi
}
-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}alert${if_post}"; then
- alert_result="$(color_alert)"
- if [ -n "$alert_result" ]; then
- alert_sed="s/${sed_pre}alert${sed_post}/\2${alert_result}\4/"
- else
- alert_sed="s/${sed_pre}alert${sed_post}//"
+element_untracked() {
+ if [ -n "$opt_status" ]; then
+ if cnt="$(status_count "^\?\? ")"; then
+ printf '%s%b?%b' "$cnt" "$STATUS_COLOR_UNTRACKED" "$COLOR_DEFAULT"
+ fi
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/"
- else
- remote_sed="s/${sed_pre}remote${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 "$alert_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
+# Main functions
+
+IF_PRE="%\{([^%{}]{1,}:){0,1}"
+IF_POST="(:[^%{}]{1,}){0,1}\}"
+SED_PRE="%{\(\([^%^{^}]*\)\:\)\{0,1\}"
+SED_POST="\(\:\([^%^{^}]*\)\)\{0,1\}}"
+
+prepare_element() {
+ if echo "$PROMPT_FORMAT" | grep -qE "${IF_PRE}${1}${IF_POST}"; then
+ result="$($2 | sed -e 's/\//\\\//g')"
+ if [ -n "$result" ]; then
+ printf '%b' "s/${SED_PRE}${1}${SED_POST}/\\\\2${result}\\\\4/"
+ else
+ printf '%b' "s/${SED_PRE}${1}${SED_POST}//"
+ fi
+ fi
}
-do_fetch=""
-
-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 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"
-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 staged element_staged)" \
+ -e "$(prepare_element unstaged element_unstaged)" \
+ -e "$(prepare_element unmerged element_unmerged)" \
+ -e "$(prepare_element untracked element_untracked)"