## vim:ft=zsh ## git support by: Frank Terbeck ## Distributed under the same BSD-ish license as zsh itself. setopt localoptions extendedglob NO_shwordsplit local gitdir gitbase gitbranch gitaction gitunstaged gitstaged gitsha1 gitmisc local -i querystaged queryunstaged local -a git_patches_applied git_patches_unapplied local -A hook_com VCS_INFO_git_getaction () { local gitdir=$1 local tmp for tmp in "${gitdir}/rebase-apply" \ "${gitdir}/rebase" \ "${gitdir}/../.dotest" ; do if [[ -d ${tmp} ]] ; then if [[ -f "${tmp}/rebasing" ]] ; then gitaction="rebase" elif [[ -f "${tmp}/applying" ]] ; then gitaction="am" else gitaction="am/rebase" fi return 0 fi done for tmp in "${gitdir}/rebase-merge/interactive" \ "${gitdir}/.dotest-merge/interactive" ; do if [[ -f "${tmp}" ]] ; then gitaction="rebase-i" return 0 fi done for tmp in "${gitdir}/rebase-merge" \ "${gitdir}/.dotest-merge" ; do if [[ -d "${tmp}" ]] ; then gitaction="rebase-m" return 0 fi done if [[ -f "${gitdir}/MERGE_HEAD" ]] ; then gitaction="merge" return 0 fi if [[ -f "${gitdir}/BISECT_LOG" ]] ; then gitaction="bisect" return 0 fi if [[ -f "${gitdir}/CHERRY_PICK_HEAD" ]] ; then if [[ -d "${gitdir}/sequencer" ]] ; then gitaction=cherry-seq else gitaction=cherry fi return 0 fi if [[ -d "${gitdir}/sequencer" ]] ; then gitaction="cherry-or-revert" return 0 fi return 1 } VCS_INFO_git_getbranch () { local gitdir=$1 tmp actiondir local gitsymref="${vcs_comm[cmd]} symbolic-ref HEAD" actiondir='' for tmp in "${gitdir}/rebase-apply" \ "${gitdir}/rebase" \ "${gitdir}/../.dotest"; do if [[ -d ${tmp} ]]; then actiondir=${tmp} break fi done if [[ -n ${actiondir} ]]; then gitbranch="$(${(z)gitsymref} 2> /dev/null)" [[ -z ${gitbranch} ]] && [[ -r ${actiondir}/head-name ]] \ && gitbranch="$(< ${actiondir}/head-name)" [[ -z ${gitbranch} || ${gitbranch} == 'detached HEAD' ]] \ && gitbranch="$(< ${gitdir}/ORIG_HEAD)" elif [[ -f "${gitdir}/MERGE_HEAD" ]] ; then gitbranch="$(${(z)gitsymref} 2> /dev/null)" [[ -z ${gitbranch} ]] && gitbranch="$(< ${gitdir}/ORIG_HEAD)" elif [[ -d "${gitdir}/rebase-merge" ]] ; then gitbranch="$(< ${gitdir}/rebase-merge/head-name)" if [[ $gitbranch == 'detached HEAD' ]]; then # get a sha1 gitbranch="$(< ${gitdir}/rebase-merge/orig-head)" fi elif [[ -d "${gitdir}/.dotest-merge" ]] ; then gitbranch="$(< ${gitdir}/.dotest-merge/head-name)" else gitbranch="$(${(z)gitsymref} 2> /dev/null)" if [[ $? -ne 0 ]] ; then gitbranch="refs/tags/$(${vcs_comm[cmd]} describe --all --exact-match HEAD 2>/dev/null)" if [[ $? -ne 0 ]] ; then gitbranch="${${"$(< $gitdir/HEAD)"}[1,7]}..." fi fi fi return 0 } VCS_INFO_git_handle_patches () { local git_applied_s git_unapplied_s gitmsg git_all git_patches_applied=(${(Oa)git_patches_applied}) git_patches_unapplied=(${(Oa)git_patches_unapplied}) (( git_all = ${#git_patches_applied} + ${#git_patches_unapplied} )) if VCS_INFO_hook 'gen-applied-string' "${git_patches_applied[@]}"; then if (( ${#git_patches_applied} )); then git_applied_s=${git_patches_applied[1]} else git_applied_s="" fi else git_applied_s=${hook_com[applied-string]} fi hook_com=() if VCS_INFO_hook 'gen-unapplied-string' "${git_patches_unapplied[@]}"; then git_patches_unapplied=${#git_patches_unapplied} else git_patches_unapplied=${hook_com[unapplied-string]} fi if (( ${#git_patches_applied} )); then zstyle -s ":vcs_info:${vcs}:${usercontext}:${rrn}" patch-format gitmsg || gitmsg="%p (%n applied)" else zstyle -s ":vcs_info:${vcs}:${usercontext}:${rrn}" nopatch-format gitmsg || gitmsg="no patch applied" fi hook_com=( applied "${git_applied_s}" unapplied "${git_patches_unapplied}" applied-n ${#git_patches_applied} unapplied-n ${#git_patches_unapplied} all-n ${git_all} ) if VCS_INFO_hook 'set-patch-format' "${gitmsg}"; then zformat -f gitmisc "${gitmsg}" "p:${hook_com[applied]}" "u:${hook_com[unapplied]}" \ "n:${#git_patches_applied}" "c:${#git_patches_unapplied}" "a:${git_all}" else gitmisc=${hook_com[patch-replace]} fi hook_com=() } gitdir=${vcs_comm[gitdir]} VCS_INFO_git_getbranch ${gitdir} gitbase=$( ${vcs_comm[cmd]} rev-parse --show-toplevel ) rrn=${gitbase:t} if zstyle -t ":vcs_info:${vcs}:${usercontext}:${rrn}" get-revision ; then gitsha1=$(${vcs_comm[cmd]} rev-parse --quiet --verify HEAD) else gitsha1='' fi gitbranch="${gitbranch##refs/[^/]##/}" if [[ -z ${gitdir} ]] || [[ -z ${gitbranch} ]] ; then return 1 fi if zstyle -t ":vcs_info:${vcs}:${usercontext}:${rrn}" "check-for-changes" ; then querystaged=1 queryunstaged=1 elif zstyle -t ":vcs_info:${vcs}:${usercontext}:${rrn}" "check-for-staged-changes" ; then querystaged=1 fi if (( querystaged || queryunstaged )) && \ [[ "$(${vcs_comm[cmd]} rev-parse --is-inside-work-tree 2> /dev/null)" == 'true' ]] ; then # Default: off - these are potentially expensive on big repositories if (( queryunstaged )) ; then ${vcs_comm[cmd]} diff --no-ext-diff --ignore-submodules=dirty --quiet --exit-code || gitunstaged=1 fi if (( querystaged )) ; then if ${vcs_comm[cmd]} rev-parse --quiet --verify HEAD &> /dev/null ; then ${vcs_comm[cmd]} diff-index --cached --quiet --ignore-submodules=dirty HEAD 2> /dev/null (( $? && $? != 128 )) && gitstaged=1 else # empty repository (no commits yet) # 4b825dc642cb6eb9a060e54bf8d69288fbee4904 is the git empty tree. ${vcs_comm[cmd]} diff-index --cached --quiet --ignore-submodules=dirty 4b825dc642cb6eb9a060e54bf8d69288fbee4904 2>/dev/null (( $? && $? != 128 )) && gitstaged=1 fi fi fi VCS_INFO_adjust VCS_INFO_git_getaction ${gitdir} local patchdir=${gitdir}/patches/${gitbranch} if [[ -d $patchdir ]] && [[ -f $patchdir/applied ]] \ && [[ -f $patchdir/unapplied ]] then git_patches_applied=(${(f)"$(< "${patchdir}/applied")"}) git_patches_unapplied=(${(f)"$(< "${patchdir}/unapplied")"}) VCS_INFO_git_handle_patches elif [[ -d "${gitdir}/rebase-merge" ]]; then patchdir="${gitdir}/rebase-merge" local p [[ -f "${patchdir}/done" ]] && for p in ${(f)"$(< "${patchdir}/done")"}; do # pick/edit/fixup/squash/reword: Add "$hash $subject" to $git_patches_applied. # exec: Add "exec ${command}" to $git_patches_applied. # (anything else): As 'exec'. p=${p/(#s)(p|pick|e|edit|r|reword|f|fixup|s|squash) /} p=${p/(#s)x /exec } git_patches_applied+=("$p") done if [[ -f "${patchdir}/git-rebase-todo" ]] ; then git_patches_unapplied=(${(f)"$(grep -v '^$' "${patchdir}/git-rebase-todo" | grep -v '^#')"}) fi VCS_INFO_git_handle_patches elif [[ -d "${gitdir}/rebase-apply" ]]; then # Fake patch names for all but current patch patchdir="${gitdir}/rebase-apply" local next="${patchdir}/next" if [[ -f $next ]]; then local cur=$(< $next) local p subject for ((p = 1; p < cur; p++)); do git_patches_applied+=("$(printf "%04d" $p) ?") done if [[ -f "${patchdir}/msg-clean" ]]; then subject="${$(< "${patchdir}/msg-clean")[(f)1]}" elif [[ -f "${patchdir}/${(l:4::0:)cur}" ]]; then local maxlines=10 line while IFS= read -r line; do if [[ "$line" == "Subject:"* ]]; then subject=${line/(#s)Subject: /} break fi (( --maxlines )) || break done < "${patchdir}/${(l:4::0:)cur}" fi if [[ -f "${patchdir}/original-commit" ]]; then if [[ -n $subject ]]; then git_patches_applied+=("$(< ${patchdir}/original-commit) $subject") else git_patches_applied+=("$(< ${patchdir}/original-commit)") fi else if [[ -n $subject ]]; then git_patches_applied+=("? $subject") else git_patches_applied+=("?") fi fi local last="$(< "${patchdir}/last")" if (( cur+1 <= last )); then git_patches_unapplied=( {$((cur+1))..$last} ) fi fi VCS_INFO_git_handle_patches elif [[ -f "${gitdir}/MERGE_HEAD" ]]; then # This is 'git merge --no-commit' local -a heads=( ${(@f)"$(<"${gitdir}/MERGE_HEAD")"} ) local subject; IFS='' read -r subject < "${gitdir}/MERGE_MSG" # $subject is the subject line of the would-be commit # Maybe we can get the subject lines of MERGE_HEAD's commits cheaply? local p for p in $heads[@]; do git_patches_applied+=("$p $subject") done unset p # Not touching git_patches_unapplied VCS_INFO_git_handle_patches elif [[ -f "${gitdir}/CHERRY_PICK_HEAD" ]]; then # 'git cherry-pick' without -n, that conflicted. (With -n, git doesn't # record the CHERRY_PICK_HEAD information anywhere, as of git 2.6.2.) # # ### 'git cherry-pick foo bar baz' only records the "remaining" part of # ### the queue in the .git dir: if 'bar' has a conflict, the .git dir # ### has a record of 'baz' being queued, but no record of 'foo' having been # ### part of the queue as well. Therefore, the %n/%c applied/unapplied # ### expandos will be memoryless: the "applied" counter will always # ### be "1". The %u/%c tuple will assume the values [(1,2), (1,1), (1,0)], # ### whereas the correct sequence would be [(1,2), (2,1), (3,0)]. local subject IFS='' read -r subject < "${gitdir}/MERGE_MSG" git_patches_applied=( "$(<${gitdir}/CHERRY_PICK_HEAD) ${subject}" ) if [[ -f "${gitdir}/sequencer/todo" ]]; then # Get the next patches, and remove the one that's in CHERRY_PICK_HEAD. git_patches_unapplied=( ${${(M)${(f)"$(<"${gitdir}/sequencer/todo")"}:#pick *}#pick } ) git_patches_unapplied[1]=() else git_patches_unapplied=() fi VCS_INFO_git_handle_patches else gitmisc='' fi backend_misc[patches]="${gitmisc}" VCS_INFO_formats "${gitaction}" "${gitbranch}" "${gitbase}" "${gitstaged}" "${gitunstaged}" "${gitsha1}" "${gitmisc}" return 0