From bd6b9fd5b1d0e93f12d052c152da0ccb0c91aafc Mon Sep 17 00:00:00 2001
From: Oliver Davies <oliver@oliverdavies.uk>
Date: Fri, 27 May 2022 18:31:52 +0100
Subject: [PATCH] chore(zsh): update prompt

---
 roles/zsh/files/config/configs/prompt.zsh |  23 +-
 roles/zsh/files/config/oh-my-zsh/git.zsh  | 281 ++++++++++++++++++++++
 2 files changed, 293 insertions(+), 11 deletions(-)
 create mode 100644 roles/zsh/files/config/oh-my-zsh/git.zsh

diff --git a/roles/zsh/files/config/configs/prompt.zsh b/roles/zsh/files/config/configs/prompt.zsh
index ece80231..3ac57cf5 100644
--- a/roles/zsh/files/config/configs/prompt.zsh
+++ b/roles/zsh/files/config/configs/prompt.zsh
@@ -1,14 +1,15 @@
-source /usr/lib/git-core/git-sh-prompt
+source $ZDOTDIR/oh-my-zsh/git.zsh
 
-setopt promptsubst
+git_prompt_prefix() {
+  local tag="$(git describe --tags 2> /dev/null)"
 
-git_prompt() {
-  local branch="$(git symbolic-ref HEAD 2> /dev/null | cut -d'/' -f3)"
-  local branch_truncated="${branch:0:30}"
-  if (( ${#branch} > ${#branch_truncated} )); then
-    branch="${branch_truncated}..."
-  fi
-
-  [ -n "${branch}" ] && echo " (${branch})"
+  [ -n "${tag}" ] && echo "%{$fg[yellow]%}${tag}%{$reset_color%} "
 }
-export PS1="in %{$fg[blue]%}%~%{$fg[yellow]%}$(git_prompt)%{$reset_color%} %(?.$.%{$fg[red]%}$)%b "
+
+git_prompt_suffix() {
+  local branch="$(git_current_branch)"
+
+  [ -n "${branch}" ] && echo " on %{$fg[green]%}${branch}%{$reset_color%}"
+}
+
+export PS1="$(git_prompt_prefix)in %{$fg[blue]%}%1d/%{$reset_color%}$(git_prompt_suffix) "
diff --git a/roles/zsh/files/config/oh-my-zsh/git.zsh b/roles/zsh/files/config/oh-my-zsh/git.zsh
new file mode 100644
index 00000000..be9fa7e6
--- /dev/null
+++ b/roles/zsh/files/config/oh-my-zsh/git.zsh
@@ -0,0 +1,281 @@
+# The git prompt's git commands are read-only and should not interfere with
+# other processes. This environment variable is equivalent to running with `git
+# --no-optional-locks`, but falls back gracefully for older versions of git.
+# See git(1) for and git-status(1) for a description of that flag.
+#
+# We wrap in a local function instead of exporting the variable directly in
+# order to avoid interfering with manually-run git commands by the user.
+function __git_prompt_git() {
+  GIT_OPTIONAL_LOCKS=0 command git "$@"
+}
+
+function git_prompt_info() {
+  # If we are on a folder not tracked by git, get out.
+  # Otherwise, check for hide-info at global and local repository level
+  if ! __git_prompt_git rev-parse --git-dir &> /dev/null \
+     || [[ "$(__git_prompt_git config --get oh-my-zsh.hide-info 2>/dev/null)" == 1 ]]; then
+    return 0
+  fi
+
+  local ref
+  ref=$(__git_prompt_git symbolic-ref --short HEAD 2> /dev/null) \
+  || ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) \
+  || return 0
+
+  # Use global ZSH_THEME_GIT_SHOW_UPSTREAM=1 for including upstream remote info
+  local upstream
+  if (( ${+ZSH_THEME_GIT_SHOW_UPSTREAM} )); then
+    upstream=$(__git_prompt_git rev-parse --abbrev-ref --symbolic-full-name "@{upstream}" 2>/dev/null) \
+    && upstream=" -> ${upstream}"
+  fi
+
+  echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref:gs/%/%%}${upstream:gs/%/%%}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}"
+}
+
+# Checks if working tree is dirty
+function parse_git_dirty() {
+  local STATUS
+  local -a FLAGS
+  FLAGS=('--porcelain')
+  if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-dirty)" != "1" ]]; then
+    if [[ "${DISABLE_UNTRACKED_FILES_DIRTY:-}" == "true" ]]; then
+      FLAGS+='--untracked-files=no'
+    fi
+    case "${GIT_STATUS_IGNORE_SUBMODULES:-}" in
+      git)
+        # let git decide (this respects per-repo config in .gitmodules)
+        ;;
+      *)
+        # if unset: ignore dirty submodules
+        # other values are passed to --ignore-submodules
+        FLAGS+="--ignore-submodules=${GIT_STATUS_IGNORE_SUBMODULES:-dirty}"
+        ;;
+    esac
+    STATUS=$(__git_prompt_git status ${FLAGS} 2> /dev/null | tail -n 1)
+  fi
+  if [[ -n $STATUS ]]; then
+    echo "$ZSH_THEME_GIT_PROMPT_DIRTY"
+  else
+    echo "$ZSH_THEME_GIT_PROMPT_CLEAN"
+  fi
+}
+
+# Gets the difference between the local and remote branches
+function git_remote_status() {
+    local remote ahead behind git_remote_status git_remote_status_detailed
+    remote=${$(__git_prompt_git rev-parse --verify ${hook_com[branch]}@{upstream} --symbolic-full-name 2>/dev/null)/refs\/remotes\/}
+    if [[ -n ${remote} ]]; then
+        ahead=$(__git_prompt_git rev-list ${hook_com[branch]}@{upstream}..HEAD 2>/dev/null | wc -l)
+        behind=$(__git_prompt_git rev-list HEAD..${hook_com[branch]}@{upstream} 2>/dev/null | wc -l)
+
+        if [[ $ahead -eq 0 ]] && [[ $behind -eq 0 ]]; then
+            git_remote_status="$ZSH_THEME_GIT_PROMPT_EQUAL_REMOTE"
+        elif [[ $ahead -gt 0 ]] && [[ $behind -eq 0 ]]; then
+            git_remote_status="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE"
+            git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE$((ahead))%{$reset_color%}"
+        elif [[ $behind -gt 0 ]] && [[ $ahead -eq 0 ]]; then
+            git_remote_status="$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE"
+            git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE$((behind))%{$reset_color%}"
+        elif [[ $ahead -gt 0 ]] && [[ $behind -gt 0 ]]; then
+            git_remote_status="$ZSH_THEME_GIT_PROMPT_DIVERGED_REMOTE"
+            git_remote_status_detailed="$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE$((ahead))%{$reset_color%}$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE_COLOR$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE$((behind))%{$reset_color%}"
+        fi
+
+        if [[ -n $ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_DETAILED ]]; then
+            git_remote_status="$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_PREFIX${remote:gs/%/%%}$git_remote_status_detailed$ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_SUFFIX"
+        fi
+
+        echo $git_remote_status
+    fi
+}
+
+# Outputs the name of the current branch
+# Usage example: git pull origin $(git_current_branch)
+# Using '--quiet' with 'symbolic-ref' will not cause a fatal error (128) if
+# it's not a symbolic ref, but in a Git repo.
+function git_current_branch() {
+  local ref
+  ref=$(__git_prompt_git symbolic-ref --quiet HEAD 2> /dev/null)
+  local ret=$?
+  if [[ $ret != 0 ]]; then
+    [[ $ret == 128 ]] && return  # no git repo.
+    ref=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) || return
+  fi
+  echo ${ref#refs/heads/}
+}
+
+
+# Gets the number of commits ahead from remote
+function git_commits_ahead() {
+  if __git_prompt_git rev-parse --git-dir &>/dev/null; then
+    local commits="$(__git_prompt_git rev-list --count @{upstream}..HEAD 2>/dev/null)"
+    if [[ -n "$commits" && "$commits" != 0 ]]; then
+      echo "$ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX$commits$ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX"
+    fi
+  fi
+}
+
+# Gets the number of commits behind remote
+function git_commits_behind() {
+  if __git_prompt_git rev-parse --git-dir &>/dev/null; then
+    local commits="$(__git_prompt_git rev-list --count HEAD..@{upstream} 2>/dev/null)"
+    if [[ -n "$commits" && "$commits" != 0 ]]; then
+      echo "$ZSH_THEME_GIT_COMMITS_BEHIND_PREFIX$commits$ZSH_THEME_GIT_COMMITS_BEHIND_SUFFIX"
+    fi
+  fi
+}
+
+# Outputs if current branch is ahead of remote
+function git_prompt_ahead() {
+  if [[ -n "$(__git_prompt_git rev-list origin/$(git_current_branch)..HEAD 2> /dev/null)" ]]; then
+    echo "$ZSH_THEME_GIT_PROMPT_AHEAD"
+  fi
+}
+
+# Outputs if current branch is behind remote
+function git_prompt_behind() {
+  if [[ -n "$(__git_prompt_git rev-list HEAD..origin/$(git_current_branch) 2> /dev/null)" ]]; then
+    echo "$ZSH_THEME_GIT_PROMPT_BEHIND"
+  fi
+}
+
+# Outputs if current branch exists on remote or not
+function git_prompt_remote() {
+  if [[ -n "$(__git_prompt_git show-ref origin/$(git_current_branch) 2> /dev/null)" ]]; then
+    echo "$ZSH_THEME_GIT_PROMPT_REMOTE_EXISTS"
+  else
+    echo "$ZSH_THEME_GIT_PROMPT_REMOTE_MISSING"
+  fi
+}
+
+# Formats prompt string for current git commit short SHA
+function git_prompt_short_sha() {
+  local SHA
+  SHA=$(__git_prompt_git rev-parse --short HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER"
+}
+
+# Formats prompt string for current git commit long SHA
+function git_prompt_long_sha() {
+  local SHA
+  SHA=$(__git_prompt_git rev-parse HEAD 2> /dev/null) && echo "$ZSH_THEME_GIT_PROMPT_SHA_BEFORE$SHA$ZSH_THEME_GIT_PROMPT_SHA_AFTER"
+}
+
+function git_prompt_status() {
+  [[ "$(__git_prompt_git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]] && return
+
+  # Maps a git status prefix to an internal constant
+  # This cannot use the prompt constants, as they may be empty
+  local -A prefix_constant_map
+  prefix_constant_map=(
+    '\?\? '     'UNTRACKED'
+    'A  '       'ADDED'
+    'M  '       'ADDED'
+    'MM '       'MODIFIED'
+    ' M '       'MODIFIED'
+    'AM '       'MODIFIED'
+    ' T '       'MODIFIED'
+    'R  '       'RENAMED'
+    ' D '       'DELETED'
+    'D  '       'DELETED'
+    'UU '       'UNMERGED'
+    'ahead'     'AHEAD'
+    'behind'    'BEHIND'
+    'diverged'  'DIVERGED'
+    'stashed'   'STASHED'
+  )
+
+  # Maps the internal constant to the prompt theme
+  local -A constant_prompt_map
+  constant_prompt_map=(
+    'UNTRACKED' "$ZSH_THEME_GIT_PROMPT_UNTRACKED"
+    'ADDED'     "$ZSH_THEME_GIT_PROMPT_ADDED"
+    'MODIFIED'  "$ZSH_THEME_GIT_PROMPT_MODIFIED"
+    'RENAMED'   "$ZSH_THEME_GIT_PROMPT_RENAMED"
+    'DELETED'   "$ZSH_THEME_GIT_PROMPT_DELETED"
+    'UNMERGED'  "$ZSH_THEME_GIT_PROMPT_UNMERGED"
+    'AHEAD'     "$ZSH_THEME_GIT_PROMPT_AHEAD"
+    'BEHIND'    "$ZSH_THEME_GIT_PROMPT_BEHIND"
+    'DIVERGED'  "$ZSH_THEME_GIT_PROMPT_DIVERGED"
+    'STASHED'   "$ZSH_THEME_GIT_PROMPT_STASHED"
+  )
+
+  # The order that the prompt displays should be added to the prompt
+  local status_constants
+  status_constants=(
+    UNTRACKED ADDED MODIFIED RENAMED DELETED
+    STASHED UNMERGED AHEAD BEHIND DIVERGED
+  )
+
+  local status_text
+  status_text="$(__git_prompt_git status --porcelain -b 2> /dev/null)"
+
+  # Don't continue on a catastrophic failure
+  if [[ $? -eq 128 ]]; then
+    return 1
+  fi
+
+  # A lookup table of each git status encountered
+  local -A statuses_seen
+
+  if __git_prompt_git rev-parse --verify refs/stash &>/dev/null; then
+    statuses_seen[STASHED]=1
+  fi
+
+  local status_lines
+  status_lines=("${(@f)${status_text}}")
+
+  # If the tracking line exists, get and parse it
+  if [[ "$status_lines[1]" =~ "^## [^ ]+ \[(.*)\]" ]]; then
+    local branch_statuses
+    branch_statuses=("${(@s/,/)match}")
+    for branch_status in $branch_statuses; do
+      if [[ ! $branch_status =~ "(behind|diverged|ahead) ([0-9]+)?" ]]; then
+        continue
+      fi
+      local last_parsed_status=$prefix_constant_map[$match[1]]
+      statuses_seen[$last_parsed_status]=$match[2]
+    done
+  fi
+
+  # For each status prefix, do a regex comparison
+  for status_prefix in ${(k)prefix_constant_map}; do
+    local status_constant="${prefix_constant_map[$status_prefix]}"
+    local status_regex=$'(^|\n)'"$status_prefix"
+
+    if [[ "$status_text" =~ $status_regex ]]; then
+      statuses_seen[$status_constant]=1
+    fi
+  done
+
+  # Display the seen statuses in the order specified
+  local status_prompt
+  for status_constant in $status_constants; do
+    if (( ${+statuses_seen[$status_constant]} )); then
+      local next_display=$constant_prompt_map[$status_constant]
+      status_prompt="$next_display$status_prompt"
+    fi
+  done
+
+  echo $status_prompt
+}
+
+# Outputs the name of the current user
+# Usage example: $(git_current_user_name)
+function git_current_user_name() {
+  __git_prompt_git config user.name 2>/dev/null
+}
+
+# Outputs the email of the current user
+# Usage example: $(git_current_user_email)
+function git_current_user_email() {
+  __git_prompt_git config user.email 2>/dev/null
+}
+
+# Output the name of the root directory of the git repository
+# Usage example: $(git_repo_name)
+function git_repo_name() {
+  local repo_path
+  if repo_path="$(__git_prompt_git rev-parse --show-toplevel 2>/dev/null)" && [[ -n "$repo_path" ]]; then
+    echo ${repo_path:t}
+  fi
+}