| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- # Display git repo information in a shell prompt
- #
- # Format:
- #
- # [repo:branch:commit] BARE BISECT/MERGE/REBASE NO-REMOTE/NO-RTB/AHEAD/BEHIND/DIVERGED WIP STASH +±xcru?
- #
- # Usage:
- #
- # Call `__gitp` in such a way that prompt expansion happens i.e. from bash's
- # `PROMPT_COMMAND` or zsh's `PROMPT`/`pre_cmd()`.
- #
- function __gitp() {
- # Preserve the exit status of the previous command.
- local exit=$?
- # Only run if git exists.
- [[ -z $(command -v git) ]] && return $exit
- # Try to get git info. Also serves to check if current folder is a git repo.
- local gitinfo ret
- gitinfo=$(git rev-parse \
- --git-dir \
- --is-bare-repository \
- --is-inside-work-tree \
- --short HEAD 2>/dev/null)
- ret="$?"
- [[ -z $gitinfo ]] && return $exit
- # Explode git info into individual variables.
- local gitdir=${gitinfo%%$'\n'*}; gitinfo=${gitinfo#*$'\n'}
- local bare=${gitinfo%%$'\n'*}; gitinfo=${gitinfo#*$'\n'}
- local worktree=${gitinfo%%$'\n'*}; gitinfo=${gitinfo#*$'\n'}
- local commit; [[ $ret = "0" ]] && commit=$gitinfo
- __gitp_cprep
- # Prepare display elements.
- [[ $bare = 'true' ]] && bare=${GITP_BARE:-"$cRed BARE"} || bare=''
- local location="$(__gitp_location $commit)"
- if [[ $worktree = 'true' && -z $GITP_SIMPLE ]]; then
- # Many things only matter if in a work tree (i.e. not bare or in gitdir).
- __gitp_status
- local op="$(__gitp_op $gitdir)"
- local remote="$(__gitp_remote)"
- local wips="$(__gitp_wips $gitdir)"
- local changes="$(__gitp_changes)"
- fi
- # Colour the location if there are local changes.
- local dirty=$(__gitp_dirty)
- # Display the git prompt.
- local formatting=${1:-' %s'}
- printf -- "$formatting" "$dirty$location$bare$op$remote$wips$changes"
- return $exit
- }
- # Get git status output for parsing.
- function __gitp_status() {
- GITP_STATUS=''
- # If available, ignore submodule work tree changes to speed up prompt.
- GITP_STATUS=$(git status --porcelain --ignore-submodules=dirty 2>/dev/null) \
- || GITP_STATUS=$(git status --porcelain)
- }
- # Initialise colour strings.
- function __gitp_cprep() {
- if [[ -n ${BASH_VERSION-} ]]; then
- cStart=${cStart:-'\[\e['}
- cEnd=${cEnd:-'m\]'}
- elif [[ -n ${ZSH_VERSION-} ]]; then
- cStart=${cStart:-'%{['}
- cEnd=${cEnd:-'m%}'}
- fi
- cReset=${cReset:-$cStart'0'$cEnd}
- cRed=$cStart'38;5;1'$cEnd
- cGre=$cStart'38;5;2'$cEnd
- cBlu=$cStart'38;5;4'$cEnd
- cMag=$cStart'38;5;5'$cEnd
- cgGre=$cStart'38;5;70'$cEnd
- cgRed=$cStart'38;5;124'$cEnd
- cgYel=$cStart'38;5;220'$cEnd
- }
- # Get location i.e. which repo/branch/commit is this?
- function __gitp_location() {
- local prefix=${GITP_PREFIX:-'['}
- local suffix=${GITP_SUFFIX:-"]"}
- local repo=$(__gitp_repo)
- local branch=$(__gitp_branch)
- [[ -z $branch ]] && branch=$(git describe --tags --exact-match 2>/dev/null)
- echo "$prefix$repo:$branch:$1$suffix"
- }
- # Determine if a repo is in the middle of an "operation" e.g. rebase, bisect.
- function __gitp_op() {
- local bisect=${GITP_BISECT:-"$cRed BISECT"}
- local merge=${GITP_MERGE:-"$cRed MERGE"}
- local rebase=${GITP_REBASE:-"$cRed REBASE"}
- if [[ -e "$1/BISECT_LOG" ]]; then
- echo "$bisect"
- elif [[ -e "$1/MERGE_HEAD" ]]; then
- echo "$merge"
- elif [[ -e "$1/rebase" || -e "$1/rebase-apply" || -e "$1/rebase-merge" || -e "$1/../.dotest" ]]; then
- echo "$rebase"
- fi
- }
- # Compare the local and remote tracking branches (upstream).
- function __gitp_remote() {
- local noremote=${GITP_NOREMOTE:-"$cMag NO-REMOTE"}
- local nortb=${GITP_NORTB:-"$cMag NO-RTB"}
- local ahead=${GITP_AHEAD:-"$cMag AHEAD"}
- local behind=${GITP_BEHIND:-"$cMag BEHIND"}
- local diverged=${GITP_DIVERGED:-"$cRed DIVERGED"}
- local remote commits count ahd=0 bhd=0
- # Count commits ahead and behind.
- if commits="$(git rev-list --left-right @{upstream}...HEAD 2>/dev/null)"; then
- [[ -n ${ZSH_VERSION-} ]] && commits=(${(f)commits})
- for commit in $commits; do
- case "$commit" in
- "<"*) ((bhd++)) ;;
- *) ((ahd++)) ;;
- esac
- done
- count="$bhd $ahd"
- fi
- # Determine what to display.
- case "$count" in
- "") # no upstream
- if [[ -z "$(git remote -v)" ]]; then
- remote="$noremote"
- elif [[ -n "$(__gitp_branch)" ]]; then
- remote="$nortb"
- fi
- ;;
- "0 0") ;; # Equal to upstream.
- "0 "*) remote="$ahead-$ahd" ;; # Ahead of upstream.
- *" 0") remote="$behind-$bhd" ;; # Behind upstream.
- *) remote="$diverged" ;; # Diverged from upstream.
- esac
- echo "$remote"
- }
- # Warn if the current commit is a work-in-progress or if there are stashed
- # changes.
- #
- # WIP commits are made with `gwip` and removed with `gunwip`.
- function __gitp_wips() {
- local wip=${GITP_WIP:-"$cRed WIP"}
- local stash=${GITP_STASH:-"$cRed STASH"}
- local wips
- if $(git log -n 1 2>/dev/null | grep -q -c "\-\-wip\-\-"); then
- wips="$wip"
- fi
- [[ -e "$1/refs/stash" ]] && wips+="$stash"
- echo "$wips"
- }
- # Work tree change indicators
- #
- # Prints symbol for each change in `git status`, up to the number of symbols
- # indicated by `GITP_CHANGES_MAX`. Gives a good visual of what has changed.
- #
- # Performance note: if the git prompt is slow, it is more because `git status`
- # is slow. This function is near-instantaneous since it is just string
- # manipulation.
- function __gitp_changes() {
- [[ -z $GITP_STATUS ]] && return
- local changes_max=${GITP_CHANGES_MAX:-20}
- local i_col=${GITP_COLOUR_INDEX:-$cgGre}
- local w_col=${GITP_COLOUR_WTREE:-$cgRed}
- local u_col=${GITP_COLOUR_UN:-$cgYel}
- local e_col=${GITP_COLOUR_END:-$cReset}
- local lines line X Y x_set y_set u_set end count=0
- # Split git status by newline into array elements.
- if [[ -n ${BASH_VERSION-} ]]; then
- IFS=$'\n'
- lines=$GITP_STATUS
- elif [[ -n ${ZSH_VERSION-} ]]; then
- lines=(${(f)GITP_STATUS})
- fi
- for line in $lines; do
- (( count+=1 ))
- (( count >= $changes_max )) && end='..' && break
- X=${line:0:1}
- Y=${line:1:1}
- [[ $X$Y = '??' ]] && u_set+="?" && continue
- [[ $X = 'U' ]] || [[ $Y = 'U' ]] && u_set+="u" && continue
- [[ $X$Y = 'DD' ]] || [[ $X$Y = 'AA' ]] && u_set+="u" && continue
- [[ $Y = 'M' ]] && y_set+="+"
- [[ $Y = 'D' ]] && y_set+="x"
- [[ $X = 'M' ]] && x_set+="+" && continue
- [[ $X = 'A' ]] && x_set+="±" && continue
- [[ $X = 'D' ]] && x_set+="x" && continue
- [[ $X = 'R' ]] && x_set+="r" && continue
- [[ $X = 'C' ]] && x_set+="c" && continue
- done
- [[ -n ${BASH_VERSION-} ]] && unset IFS
- echo " $i_col$x_set$w_col$y_set$u_col$u_set$e_col$end$cReset"
- }
- # Check if a repo is modified
- function __gitp_dirty() {
- local clean=${GITP_CLEAN:-$cReset$cGre}
- local dirty=${GITP_DIRTY:-$cReset$cBlu}
- [[ -z $GITP_STATUS ]] && echo "$clean" || echo "$dirty"
- }
- # Get repository name
- #
- # Check for a name in git remotes, between ':' or '/' and a space.
- function __gitp_repo() {
- local repo="$(git remote -v | head -n 1 2>/dev/null)"
- repo=${repo##*[:|/]}
- repo=${repo% *}
- printf "${repo%.git}"
- }
- # Get current branch name
- #
- # Prints the branch name or tag of the current commit, if any.
- function __gitp_branch() {
- local ref
- ref=$(git symbolic-ref --quiet HEAD 2>/dev/null)
- printf "${ref#refs/heads/}"
- }
- # Toggle git prompt's "simple" mode.
- # This mode will simply display the location information and bare state. It is
- # useful for slow machines (e.g. tablets) or slow repositories (e.g. hundreds of
- # changed files). For a more permanent setting, set the `GITP_SIMPLE` variable
- # to a non-zero string before `__gitp()` is run.
- function toggle_gitp_simple() {
- [[ -z $GITP_SIMPLE ]] && GITP_SIMPLE=true || GITP_SIMPLE=''
- }
|