在 Shell 中显示 Git 分支对于提高效率非常有帮助,毕竟 Bug 不会在你准备好了才出现,在 Branch 之间切来切去已经习以为常,不喜欢折腾的话可以直接 Oh My Zsh,简单易用不浪费生命。但是对于一些极端环境,例如小容器内部,或者没有太多权限的服务环境下,Oh My Zsh 就显得不适用了,所以我们来探索一个 DIY 版本。
Bash/Zsh 环境修改 Prompt 的原理就是修改 PS1 环境变量。原理很简单,实现也并不复杂,毕竟 git describe git rev-parse git show-ref git branch git status 之类的二级命令累加即可实现,但是如果使用 time git status 执行一下就会发现,执行的时间是不可控的,尤其是在大的 Repo 里面,执行时间往往是百毫秒级的,这样对于使用体验来说是极差的,毕竟执行一次回车,要等待大于人眼反映速度的时间就会感到明显的卡顿,所以在设计时首先需要考虑的因素是执行时间。
对于执行时间的优化可以通过下面这种方法来解决。每个 Repo 都有一个 .git/HEAD 文件用来保存当前 HEAD 所在的 commit 的 SHA1 值,只要我们每次执行时首先判断这个 SHA1 值是否变化才决定是否进行 branch 重新计算即可。经实验执行时间可以控制在 10ms 以内。

GIT_NAME_TITLE=''
GIT_NAME_CONTENT=''
GIT_NAME_LEFT=''
GIT_NAME_RIGHT=''
GIT_NAME_HEAD=''

function git_branch_internal()
{
    local dir="."
    until [ "${dir}" -ef / ]; do
        if [ -f "${dir}/.git/HEAD" ]; then
            local head=$(< "${dir}/.git/HEAD")
            if [[ ${head} == ${GIT_NAME_HEAD} ]]; then
                return
            fi
            GIT_NAME_HEAD=${head}
            if [[ $head =~ ^ref\:\ refs\/heads\/* ]]; then
                GIT_NAME_TITLE="branch"
                GIT_NAME_CONTENT="${head#*/*/}"
            else
                local describe=$(git describe --tags --abbrev=7 2> /dev/null)
                if [ -n "${describe}" ]; then
                    GIT_NAME_TITLE="tag"
                    GIT_NAME_CONTENT=${describe}
                else
                    GIT_NAME_TITLE="commit"
                    GIT_NAME_CONTENT=${head:0:7}
                fi
            fi
            GIT_NAME_LEFT=":["
            GIT_NAME_RIGHT="]"
            return
        fi
        dir="../${dir}"
    done
    GIT_NAME_TITLE=''
    GIT_NAME_CONTENT=''
    GIT_NAME_LEFT=''
    GIT_NAME_RIGHT=''
    GIT_NAME_HEAD=''
}

# Git branch perception
function git_zsh_precmd()
{
    git_branch_internal
    PS1="${PS1_BAK}%{$fg_bold[blue]%}${GIT_NAME_TITLE}${GIT_NAME_LEFT}%{$fg_bold[red]%}${GIT_NAME_CONTENT}%{$fg_bold[blue]%}${GIT_NAME_RIGHT}%% %{$reset_color%}"
}

# color for PS1
black=$'\[\e[1;30m\]'
red=$'\[\e[1;31m\]'
green=$'\[\e[1;32m\]'
yellow=$'\[\e[1;33m\]'
blue=$'\[\e[1;34m\]'
magenta=$'\[\e[1;35m\]'
cyan=$'\[\e[1;36m\]'
white=$'\[\e[1;37m\]'
normal=$'\[\e[m\]'

# Git prompt for branch infomation
function git_prompt()
{
    if [ "${PS1_BAK-NODEFINE}" = "NODEFINE" ] ; then
        PS1_BAK=${PS1-}
        if [[ ${SHELL} =~ .*bash$ ]]; then
            PROMPT_COMMAND_BAK=${PROMPT_COMMAND-}
            PROMPT_COMMAND="git_branch_internal;${PROMPT_COMMAND-}"
            PS1="$PS1$blue\$GIT_NAME_TITLE\$GIT_NAME_LEFT$red\$GIT_NAME_CONTENT$blue\$GIT_NAME_RIGHT\$ $normal"
        elif [[ ${SHELL} =~ .*zsh$ ]]; then
            autoload -U colors && colors
            precmd_functions=(git_zsh_precmd)
        fi
    else
        if [[ ${SHELL} =~ .*bash$ ]]; then
            PROMPT_COMMAND=${PROMPT_COMMAND_BAK-}
            unset PROMPT_COMMAND_BAK
        elif [[ ${SHELL} =~ .*zsh$ ]]; then
            unset precmd_functions
        fi
        PS1=${PS1_BAK-}
        unset PS1_BAK
        GIT_NAME_HEAD=''
    fi
}


参考文档

  1. A1.6 Appendix A: Git in Other Environments - Git in Bash
  2. https://github.com/git/git/tree/master/contrib/completion
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐