]> git.madduck.net Git - etc/zsh.git/blob - .zsh/zshrc/60_vcsprompt

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

update URL and copyright
[etc/zsh.git] / .zsh / zshrc / 60_vcsprompt
1 # zshrc/60_vcsprompt
2 #
3 # Make git information available to the prompt
4 #
5 # Copyright © 1994–2017 martin f. krafft <madduck@madduck.net>
6 # Released under the terms of the Artistic Licence 2.0
7 #
8 # Source repository: http://git.madduck.net/v/etc/zsh.git
9 #
10 # Shamelessly based on http://glandium.org/blog/?p=170
11 #
12
13 __on_networkfs()
14 {
15   case $(df -T . | sed -rne '$s,^[^[:space:]]+[[:space:]]+([^[:space:]]+).*,\1,p') in
16     (cifs|nfs) return 0;;
17   esac
18   return 1
19 }
20
21 __git_get_reporoot()
22 {
23   # return the full path to the root of the current git repository
24   [ -d "$GIT_DIR" ] && echo "$GIT_DIR" && return 0
25   local dir; dir="$PWD/$(git rev-parse --show-cdup)"
26   # do not use --show-toplevel because it resolves symlinks
27   echo $dir:a
28 }
29
30 __git_get_branch()
31 {
32   # return the name of the git branch we're on
33   local ref gitdir
34   gitdir="$(git rev-parse --git-dir)"
35   ref=$(git --git-dir="$gitdir" symbolic-ref -q HEAD 2>/dev/null \
36      || git --git-dir="$gitdir" name-rev --name-only HEAD 2>/dev/null) || return 1
37   echo "${ref#refs/heads/}"
38 }
39
40 __git_print_preprompt()
41 {
42   [ "$(git config --get core.bare)" = false ] || return
43   __on_networkfs && return
44
45   local output
46   output=(${(f):-"$(git diff --stat --relative 2>/dev/null)"})
47   if [[ ${#output} -gt 1 ]]; then
48     echo changes on filesystem:
49     print "${${(F)output[1,-2]}//\.\.\./…}"
50   fi
51   output=(${(f):-"$(git diff --cached --stat --relative 2>/dev/null)"})
52   if [[ ${#output} -gt 1 ]]; then
53     echo cached/staged changes:
54     print "${${(F)output[1,-2]}//\.\.\./…}"
55   fi
56 }
57
58 __hg_get_reporoot()
59 {
60   hg root
61 }
62
63 __hg_get_branch()
64 {
65   echo "hg:$(hg branch)"
66 }
67
68 __bzr_get_reporoot()
69 {
70   local reporoot
71   reporoot="$(bzr info | sed -rne 's, *branch root: ,,p')"
72   case "$reporoot" in
73     .) echo "$PWD";;
74     *) echo "$reporoot";;
75   esac
76 }
77
78 __bzr_get_branch()
79 {
80   local branch revno
81   bzr version-info | while read i j; do
82       case "$i" in
83         revno:) revno="$j";;
84         branch-nick:) branch="$j";;
85       esac
86     done
87   echo "bzr:${branch}@$revno"
88 }
89
90 __vcs_get_repo_type()
91 {
92   # return the type of the closest repository in the path hierarchy
93   local dir
94   while true; do
95     [ -d ${dir}.git ] && echo git && break
96     [ -d "$GIT_DIR" ] && echo git && break
97     [ -d ${dir}.bzr ] && echo bzr && break
98     [ -d ${dir}.hg ] && echo hg && break
99     [ "$(readlink -f ${dir:-.})" = / ] && echo NONE && break
100     dir="../$dir"
101   done
102 }
103
104 __vcs_get_prompt_path_components()
105 {
106   # return formatted path components (prefix branch postfix) given
107   # the repository root and the branch.
108
109   # shortcut: if there are no arguments, return a default prompt
110   if [ -z "${1:-}" ]; then
111     pwdnamed="${(%):-%${_PROMPT_PATH_MAXLEN}<…<%~%<<}"
112     echo "$pwdnamed"
113     return
114   fi
115
116   local reporoot branch
117   reporoot="${1%%/}"
118   branch="$2"
119
120   # replace named directories in the PWD, we need thi for the proper component
121   # count later
122   local pwdnamed
123   pwdnamed="${(%):-%~}"
124
125   # store paths in arrays for component count calculation
126   typeset -la apwd apwdnamed areporoot
127   apwd=(${(s:/:)PWD})
128   apwdnamed=(${(s:/:)pwdnamed})
129   areporoot=(${(s:/:)reporoot})
130
131   # get the number of leading and trailing path components. Since we're using
132   # %~ later and then /home/madduck suddenly becomes ~, which is 1, not
133   # 2 components, we calculate the leading component count by using the named
134   # path and the number of post components
135   local precomps postcomps
136   postcomps=$(($#apwd - $#areporoot))
137   precomps=$(($#apwdnamed - $postcomps))
138
139   local postfix
140   (( $postcomps > 0 )) && postfix="${(%):-%${postcomps}~}"
141
142   # we don't want the prompt to get too long, so keep the total prompt length
143   # under $_PROMPT_PATH_MAXLEN (25), but ensure that the prefix is not shorter
144   # than $_PROMPT_PATH_MINLEN (10), no matter what
145   local prelen minlen prefix
146   prelen=$((${_PROMPT_PATH_MAXLEN:-25} - $#branch - $#postfix))
147   minlen=${_PROMPT_PATH_MINLEN:-10}
148   (( $prelen < $minlen )) && prelen=$minlen
149   prefix="${(%):-%${prelen}<…<%-${precomps}~%<<}"
150
151   echo "'$prefix'" "'$branch'" "'$postfix'"
152 }
153
154 __vcs_set_prompt_variables()
155 {
156   # set psvar[1..3] depending on repo type, or just psvar[1] if no repo found
157   local reporoot branch repotype
158   repotype="${1:-$(__vcs_get_repo_type)}"
159
160   case "$repotype" in
161     git)
162       reporoot="$(__git_get_reporoot)" ||
163         { error "could not determine git repository root"; return 1 }
164       branch="$(__git_get_branch)" ||
165         { error "could not determine git branch"; return 1 }
166       if [ -n "$VCSH_REPO_NAME" ]; then
167         # if vcsh is used to get a subshell, then the repo root is the home
168         # directory, but we want to indicate the vcsh context too:
169         eval set -- $(__vcs_get_prompt_path_components "$HOME" "$branch")
170         set -- "vcsh:$VCSH_REPO_NAME" "$2" "$3"
171       else
172         eval set -- $(__vcs_get_prompt_path_components "$reporoot" "$branch")
173         if [ -d "$GIT_DIR" ]; then
174           # poor man's replace until I find out how to do named dirs properly
175           # here:
176           local _D="${GIT_DIR/$HOME/~}"
177           set -- "$_D" "$2" "${${1#$_D}%/}"
178         fi
179       fi
180       ;;
181     hg)
182       reporoot="$(__hg_get_reporoot)" ||
183         { error "could not determine hg repository root"; return 1 }
184       branch="$(__hg_get_branch)" ||
185         { error "could not determine hg branch"; return 1 }
186       eval set -- $(__vcs_get_prompt_path_components "$reporoot" "$branch")
187       ;;
188     bzr)
189       reporoot="$(__bzr_get_reporoot)" ||
190         { error "could not determine bzr repository root"; return 1 }
191       branch="$(__bzr_get_branch)" ||
192         { error "could not determine bzr branch"; return 1 }
193       eval set -- $(__vcs_get_prompt_path_components "$reporoot" "$branch")
194       ;;
195     *)
196       case "$repotype" in
197         NONE) :;;
198         *) warn "$repotype repositories not (yet) supported in the prompt";;
199       esac
200       local p="%${MAXLEN}<…<%~%<<"
201       #TODO find a better way so we don't have to nuke $psvar, but since the
202       #     %(nv.true.false) check for prompts checks element count, not
203       #     content, that's all we get for now
204       psvar=("${(%)p}")
205       return
206   esac
207
208   psvar[1,3]=($1 $2 $3)
209 }
210
211 __vcs_print_preprompt()
212 {
213   local reporoot
214   repotype="${1:-$(__vcs_get_repo_type)}"
215
216   case "$repotype" in
217     git)
218       __git_print_preprompt
219       ;;
220   esac
221 }
222
223 if ! is_root; then
224   # too dangerous to be run as root
225
226   _update_vcs_prompt_vars_if_vcs_ran() {
227     local vcs="$(__vcs_get_repo_type)"
228     case "$(history $(($HISTCMD - 1)))" in
229       # $vcs appeared in last command, so be sure to update
230       *${vcs}*) __vcs_set_prompt_variables "$vcs"
231     esac
232   }
233   precmd_functions+=_update_vcs_prompt_vars_if_vcs_ran
234
235   _update_vcs_prompt_vars() {
236     __vcs_set_prompt_variables
237   }
238   chpwd_functions+=_update_vcs_prompt_vars
239
240   _print_preprompt() {
241     [[ $? -eq 0 ]] && __vcs_print_preprompt
242   }
243   precmd_functions+=_print_preprompt
244
245   # call it once
246   _update_vcs_prompt_vars
247 fi
248
249 # vim:ft=zsh