]>
Commit | Line | Data |
---|---|---|
fa250235 BB |
1 | # Exposes Git repository information via the $git_info associative array. |
2 | # | |
3 | # Authors: | |
4 | # Sorin Ionescu <sorin.ionescu@gmail.com> | |
5 | # | |
6 | ||
7 | # Gets the Git special action (am, bisect, cherry, merge, rebase). | |
8 | # Borrowed from vcs_info and edited. | |
9 | function _git-action { | |
10 | local action_dir | |
11 | local git_dir="$(git-dir)" | |
12 | local apply_formatted | |
13 | local bisect_formatted | |
14 | local cherry_pick_formatted | |
15 | local cherry_pick_sequence_formatted | |
16 | local merge_formatted | |
17 | local rebase_formatted | |
18 | local rebase_interactive_formatted | |
19 | local rebase_merge_formatted | |
20 | ||
21 | for action_dir in \ | |
22 | "${git_dir}/rebase-apply" \ | |
23 | "${git_dir}/rebase" \ | |
24 | "${git_dir}/../.dotest" | |
25 | do | |
26 | if [[ -d "$action_dir" ]] ; then | |
27 | zstyle -s ':git:info:action:apply' format 'apply_formatted' || apply_formatted='apply' | |
28 | zstyle -s ':git:info:action:rebase' format 'rebase_formatted' || rebase_formatted='rebase' | |
29 | ||
30 | if [[ -f "${action_dir}/rebasing" ]] ; then | |
31 | print "$rebase_formatted" | |
32 | elif [[ -f "${action_dir}/applying" ]] ; then | |
33 | print "$apply_formatted" | |
34 | else | |
35 | print "${rebase_formatted}/${apply_formatted}" | |
36 | fi | |
37 | ||
38 | return 0 | |
39 | fi | |
40 | done | |
41 | ||
42 | for action_dir in \ | |
43 | "${git_dir}/rebase-merge/interactive" \ | |
44 | "${git_dir}/.dotest-merge/interactive" | |
45 | do | |
46 | if [[ -f "$action_dir" ]]; then | |
47 | zstyle -s ':git:info:action:rebase-interactive' format 'rebase_interactive_formatted' || rebase_interactive_formatted='rebase-interactive' | |
48 | print "$rebase_interactive_formatted" | |
49 | return 0 | |
50 | fi | |
51 | done | |
52 | ||
53 | for action_dir in \ | |
54 | "${git_dir}/rebase-merge" \ | |
55 | "${git_dir}/.dotest-merge" | |
56 | do | |
57 | if [[ -d "$action_dir" ]]; then | |
58 | zstyle -s ':git:info:action:rebase-merge' format 'rebase_merge_formatted' || rebase_merge_formatted='rebase-merge' | |
59 | print "$rebase_merge_formatted" | |
60 | return 0 | |
61 | fi | |
62 | done | |
63 | ||
64 | if [[ -f "${git_dir}/MERGE_HEAD" ]]; then | |
65 | zstyle -s ':git:info:action:merge' format 'merge_formatted' || merge_formatted='merge' | |
66 | print "$merge_formatted" | |
67 | return 0 | |
68 | fi | |
69 | ||
70 | if [[ -f "${git_dir}/CHERRY_PICK_HEAD" ]]; then | |
71 | if [[ -d "${git_dir}/sequencer" ]] ; then | |
72 | zstyle -s ':git:info:action:cherry-pick-sequence' format 'cherry_pick_sequence_formatted' || cherry_pick_sequence_formatted='cherry-pick-sequence' | |
73 | print "$cherry_pick_sequence_formatted" | |
74 | else | |
75 | zstyle -s ':git:info:action:cherry-pick' format 'cherry_pick_formatted' || cherry_pick_formatted='cherry-pick' | |
76 | print "$cherry_pick_formatted" | |
77 | fi | |
78 | ||
79 | return 0 | |
80 | fi | |
81 | ||
82 | if [[ -f "${git_dir}/BISECT_LOG" ]]; then | |
83 | zstyle -s ':git:info:action:bisect' format 'bisect_formatted' || bisect_formatted='bisect' | |
84 | print "$bisect_formatted" | |
85 | return 0 | |
86 | fi | |
87 | ||
88 | return 1 | |
89 | } | |
90 | ||
91 | # Gets the Git status information. | |
92 | function git-info { | |
93 | # Extended globbing is needed to parse repository status. | |
94 | setopt LOCAL_OPTIONS | |
95 | setopt EXTENDED_GLOB | |
96 | ||
97 | local action | |
98 | local action_format | |
99 | local action_formatted | |
100 | local added=0 | |
101 | local added_format | |
102 | local added_formatted | |
103 | local ahead=0 | |
104 | local ahead_and_behind | |
105 | local ahead_and_behind_cmd | |
106 | local ahead_format | |
107 | local ahead_formatted | |
108 | local ahead_or_behind | |
109 | local behind=0 | |
110 | local behind_format | |
111 | local behind_formatted | |
112 | local branch | |
113 | local branch_format | |
114 | local branch_formatted | |
115 | local branch_info | |
116 | local clean | |
117 | local clean_formatted | |
118 | local commit | |
119 | local commit_format | |
120 | local commit_formatted | |
121 | local deleted=0 | |
122 | local deleted_format | |
123 | local deleted_formatted | |
124 | local dirty=0 | |
125 | local dirty_format | |
126 | local dirty_formatted | |
127 | local ignore_submodules | |
128 | local indexed=0 | |
129 | local indexed_format | |
130 | local indexed_formatted | |
131 | local -A info_formats | |
132 | local info_format | |
133 | local modified=0 | |
134 | local modified_format | |
135 | local modified_formatted | |
136 | local position | |
137 | local position_format | |
138 | local position_formatted | |
139 | local remote | |
140 | local remote_cmd | |
141 | local remote_format | |
142 | local remote_formatted | |
143 | local renamed=0 | |
144 | local renamed_format | |
145 | local renamed_formatted | |
146 | local stashed=0 | |
147 | local stashed_format | |
148 | local stashed_formatted | |
149 | local status_cmd | |
150 | local status_mode | |
151 | local unindexed=0 | |
152 | local unindexed_format | |
153 | local unindexed_formatted | |
154 | local unmerged=0 | |
155 | local unmerged_format | |
156 | local unmerged_formatted | |
157 | local untracked=0 | |
158 | local untracked_format | |
159 | local untracked_formatted | |
160 | ||
161 | # Clean up previous $git_info. | |
162 | unset git_info | |
163 | typeset -gA git_info | |
164 | ||
165 | # Return if not inside a Git repository work tree. | |
166 | if [ "$(git rev-parse --is-inside-work-tree 2> /dev/null)" != 'true' ]; then | |
167 | return 1 | |
168 | fi | |
169 | ||
170 | if (( $# > 0 )); then | |
171 | if [[ "$1" == [Oo][Nn] ]]; then | |
172 | git config --bool prompt.showinfo true | |
173 | elif [[ "$1" == [Oo][Ff][Ff] ]]; then | |
174 | git config --bool prompt.showinfo false | |
175 | else | |
176 | print "usage: $0 [ on | off ]" >&2 | |
177 | fi | |
178 | return 0 | |
179 | fi | |
180 | ||
181 | # Ignore submodule status. | |
182 | zstyle -s ':git:status:ignore' submodules 'ignore_submodules' | |
183 | ||
184 | # Format commit. | |
185 | zstyle -s ':git:info:commit' format 'commit_format' | |
186 | if [[ -n "$commit_format" ]]; then | |
187 | commit="$(git rev-parse HEAD 2> /dev/null)" | |
188 | if [[ -n "$commit" ]]; then | |
189 | zformat -f commit_formatted "$commit_format" "c:$commit" | |
190 | fi | |
191 | fi | |
192 | ||
193 | # Format stashed. | |
194 | zstyle -s ':git:info:stashed' format 'stashed_format' | |
195 | if [[ -n "$stashed_format" && -f "$(git-dir)/refs/stash" ]]; then | |
196 | stashed="$(git stash list 2> /dev/null | wc -l | awk '{print $1}')" | |
197 | if [[ -n "$stashed" ]]; then | |
198 | zformat -f stashed_formatted "$stashed_format" "S:$stashed" | |
199 | fi | |
200 | fi | |
201 | ||
202 | # Format action. | |
203 | zstyle -s ':git:info:action' format 'action_format' | |
204 | if [[ -n "$action_format" ]]; then | |
205 | action="$(_git-action)" | |
206 | if [[ -n "$action" ]]; then | |
207 | zformat -f action_formatted "$action_format" "s:$action" | |
208 | fi | |
209 | fi | |
210 | ||
211 | # Get the branch. | |
212 | branch="${$(git symbolic-ref HEAD 2> /dev/null)#refs/heads/}" | |
213 | ||
214 | # Format branch. | |
215 | zstyle -s ':git:info:branch' format 'branch_format' | |
216 | if [[ -n "$branch" && -n "$branch_format" ]]; then | |
217 | zformat -f branch_formatted "$branch_format" "b:$branch" | |
218 | fi | |
219 | ||
220 | # Format position. | |
221 | zstyle -s ':git:info:position' format 'position_format' | |
222 | if [[ -z "$branch" && -n "$position_format" ]]; then | |
223 | position="$(git describe --contains --all HEAD 2> /dev/null)" | |
224 | if [[ -n "$position" ]]; then | |
225 | zformat -f position_formatted "$position_format" "p:$position" | |
226 | fi | |
227 | fi | |
228 | ||
229 | # Format remote. | |
230 | zstyle -s ':git:info:remote' format 'remote_format' | |
231 | if [[ -n "$branch" && -n "$remote_format" ]]; then | |
232 | # Gets the remote name. | |
233 | remote_cmd='git rev-parse --symbolic-full-name --verify HEAD@{upstream}' | |
234 | remote="${$(${(z)remote_cmd} 2> /dev/null)##refs/remotes/}" | |
235 | if [[ -n "$remote" ]]; then | |
236 | zformat -f remote_formatted "$remote_format" "R:$remote" | |
237 | fi | |
238 | fi | |
239 | ||
240 | zstyle -s ':git:info:ahead' format 'ahead_format' | |
241 | zstyle -s ':git:info:behind' format 'behind_format' | |
242 | if [[ -n "$branch" && ( -n "$ahead_format" || -n "$behind_format" ) ]]; then | |
243 | # Gets the commit difference counts between local and remote. | |
244 | ahead_and_behind_cmd='git rev-list --count --left-right HEAD...@{upstream}' | |
245 | ||
246 | # Get ahead and behind counts. | |
247 | ahead_and_behind="$(${(z)ahead_and_behind_cmd} 2> /dev/null)" | |
248 | ||
249 | # Format ahead. | |
250 | if [[ -n "$ahead_format" ]]; then | |
251 | ahead="$ahead_and_behind[(w)1]" | |
252 | if (( ahead > 0 )); then | |
253 | zformat -f ahead_formatted "$ahead_format" "A:$ahead" | |
254 | fi | |
255 | fi | |
256 | ||
257 | # Format behind. | |
258 | if [[ -n "$behind_format" ]]; then | |
259 | behind="$ahead_and_behind[(w)2]" | |
260 | if (( behind > 0 )); then | |
261 | zformat -f behind_formatted "$behind_format" "B:$behind" | |
262 | fi | |
263 | fi | |
264 | fi | |
265 | ||
266 | # Get status type. | |
267 | if ! zstyle -t ':git:info' verbose; then | |
268 | # Format indexed. | |
269 | zstyle -s ':git:info:indexed' format 'indexed_format' | |
270 | if [[ -n "$indexed_format" ]]; then | |
271 | (( | |
272 | indexed+=$( | |
273 | git diff-index \ | |
274 | --no-ext-diff \ | |
275 | --name-only \ | |
276 | --cached \ | |
277 | --ignore-submodules=${ignore_submodules:-none} \ | |
278 | HEAD \ | |
279 | 2> /dev/null \ | |
280 | | wc -l | |
281 | ) | |
282 | )) | |
283 | if (( indexed > 0 )); then | |
284 | zformat -f indexed_formatted "$indexed_format" "i:$indexed" | |
285 | fi | |
286 | fi | |
287 | ||
288 | # Format unindexed. | |
289 | zstyle -s ':git:info:unindexed' format 'unindexed_format' | |
290 | if [[ -n "$unindexed_format" ]]; then | |
291 | (( | |
292 | unindexed+=$( | |
293 | git diff-files \ | |
294 | --no-ext-diff \ | |
295 | --name-only \ | |
296 | --ignore-submodules=${ignore_submodules:-none} \ | |
297 | 2> /dev/null \ | |
298 | | wc -l | |
299 | ) | |
300 | )) | |
301 | if (( unindexed > 0 )); then | |
302 | zformat -f unindexed_formatted "$unindexed_format" "I:$unindexed" | |
303 | fi | |
304 | fi | |
305 | ||
306 | # Format untracked. | |
307 | zstyle -s ':git:info:untracked' format 'untracked_format' | |
308 | if [[ -n "$untracked_format" ]]; then | |
309 | (( | |
310 | untracked+=$( | |
311 | git ls-files \ | |
312 | --other \ | |
313 | --exclude-standard \ | |
314 | 2> /dev/null \ | |
315 | | wc -l | |
316 | ) | |
317 | )) | |
318 | if (( untracked > 0 )); then | |
319 | zformat -f untracked_formatted "$untracked_format" "u:$untracked" | |
320 | fi | |
321 | fi | |
322 | ||
323 | (( dirty = indexed + unindexed + untracked )) | |
324 | else | |
325 | # Use porcelain status for easy parsing. | |
326 | status_cmd="git status --porcelain --ignore-submodules=${ignore_submodules:-none}" | |
327 | ||
328 | # Get current status. | |
329 | while IFS=$'\n' read line; do | |
330 | # Count added, deleted, modified, renamed, unmerged, untracked, dirty. | |
331 | # T (type change) is undocumented, see http://git.io/FnpMGw. | |
332 | # For a table of scenarii, see http://i.imgur.com/2YLu1.png. | |
333 | [[ "$line" == ([ACDMT][\ MT]|[ACMT]D)\ * ]] && (( added++ )) | |
334 | [[ "$line" == [\ ACMRT]D\ * ]] && (( deleted++ )) | |
335 | [[ "$line" == ?[MT]\ * ]] && (( modified++ )) | |
336 | [[ "$line" == R?\ * ]] && (( renamed++ )) | |
337 | [[ "$line" == (AA|DD|U?|?U)\ * ]] && (( unmerged++ )) | |
338 | [[ "$line" == \?\?\ * ]] && (( untracked++ )) | |
339 | (( dirty++ )) | |
340 | done < <(${(z)status_cmd} 2> /dev/null) | |
341 | ||
342 | # Format added. | |
343 | if (( added > 0 )); then | |
344 | zstyle -s ':git:info:added' format 'added_format' | |
345 | zformat -f added_formatted "$added_format" "a:$added" | |
346 | fi | |
347 | ||
348 | # Format deleted. | |
349 | if (( deleted > 0 )); then | |
350 | zstyle -s ':git:info:deleted' format 'deleted_format' | |
351 | zformat -f deleted_formatted "$deleted_format" "d:$deleted" | |
352 | fi | |
353 | ||
354 | # Format modified. | |
355 | if (( modified > 0 )); then | |
356 | zstyle -s ':git:info:modified' format 'modified_format' | |
357 | zformat -f modified_formatted "$modified_format" "m:$modified" | |
358 | fi | |
359 | ||
360 | # Format renamed. | |
361 | if (( renamed > 0 )); then | |
362 | zstyle -s ':git:info:renamed' format 'renamed_format' | |
363 | zformat -f renamed_formatted "$renamed_format" "r:$renamed" | |
364 | fi | |
365 | ||
366 | # Format unmerged. | |
367 | if (( unmerged > 0 )); then | |
368 | zstyle -s ':git:info:unmerged' format 'unmerged_format' | |
369 | zformat -f unmerged_formatted "$unmerged_format" "U:$unmerged" | |
370 | fi | |
371 | ||
372 | # Format untracked. | |
373 | if (( untracked > 0 )); then | |
374 | zstyle -s ':git:info:untracked' format 'untracked_format' | |
375 | zformat -f untracked_formatted "$untracked_format" "u:$untracked" | |
376 | fi | |
377 | fi | |
378 | ||
379 | # Format dirty and clean. | |
380 | if (( dirty > 0 )); then | |
381 | zstyle -s ':git:info:dirty' format 'dirty_format' | |
382 | zformat -f dirty_formatted "$dirty_format" "D:$dirty" | |
383 | else | |
384 | zstyle -s ':git:info:clean' format 'clean_formatted' | |
385 | fi | |
386 | ||
387 | # Format info. | |
388 | zstyle -a ':git:info:keys' format 'info_formats' | |
389 | for info_format in ${(k)info_formats}; do | |
390 | zformat -f REPLY "$info_formats[$info_format]" \ | |
391 | "a:$added_formatted" \ | |
392 | "A:$ahead_formatted" \ | |
393 | "B:$behind_formatted" \ | |
394 | "b:$branch_formatted" \ | |
395 | "C:$clean_formatted" \ | |
396 | "c:$commit_formatted" \ | |
397 | "d:$deleted_formatted" \ | |
398 | "D:$dirty_formatted" \ | |
399 | "i:$indexed_formatted" \ | |
400 | "I:$unindexed_formatted" \ | |
401 | "m:$modified_formatted" \ | |
402 | "p:$position_formatted" \ | |
403 | "R:$remote_formatted" \ | |
404 | "r:$renamed_formatted" \ | |
405 | "s:$action_formatted" \ | |
406 | "S:$stashed_formatted" \ | |
407 | "U:$unmerged_formatted" \ | |
408 | "u:$untracked_formatted" | |
409 | git_info[$info_format]="$REPLY" | |
410 | done | |
411 | ||
412 | unset REPLY | |
413 | ||
414 | return 0 | |
415 | } | |
416 | ||
417 | git-info "$@" |