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