#!/usr/bin/env bash # -*- coding: utf-8 -*- # gitstatus.sh -- produce the current git repo status on STDOUT # Functionally equivalent to 'gitstatus.py', but written in bash (not python). # # Alan K. Stebbens [http://github.com/aks] set -u if [[ -z "${__GIT_PROMPT_DIR:+x}" ]]; then SOURCE="${BASH_SOURCE[0]}" while [[ -h "${SOURCE}" ]]; do DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" SOURCE="$(readlink "${SOURCE}")" [[ "${SOURCE}" != /* ]] && SOURCE="${DIR}/${SOURCE}" done __GIT_PROMPT_DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" fi if [[ "${__GIT_PROMPT_IGNORE_SUBMODULES:-0}" == "1" ]]; then _ignore_submodules="--ignore-submodules" else _ignore_submodules="" fi if [[ "${__GIT_PROMPT_WITH_USERNAME_AND_REPO:-0}" == "1" ]]; then # returns "user/repo" from remote.origin.url git variable # # supports urls: # https://user@bitbucket.org/user/repo.git # https://github.com/user/repo.git # git@github.com:user/repo.git # remote_url=$(git config --get remote.origin.url | sed 's|^.*//||; s/.*@//; s/[^:/]\+[:/]//; s/.git$//') else remote_url='.' fi # shellcheck disable=SC2086 gitstatus=$( LC_ALL=C git --no-optional-locks status ${_ignore_submodules} --untracked-files="${__GIT_PROMPT_SHOW_UNTRACKED_FILES:-normal}" --porcelain --branch ) # if the status is fatal, exit now [[ ! "${?}" ]] && exit 0 git_dir="$(git rev-parse --git-dir 2>/dev/null)" [[ -z "${git_dir:+x}" ]] && exit 0 __git_prompt_read () { local f="${1}" shift [[ -r "${f}" ]] && read -r "${@}" <"${f}" } state="" step="" total="" if [[ -d "${git_dir}/rebase-merge" ]]; then __git_prompt_read "${git_dir}/rebase-merge/msgnum" step __git_prompt_read "${git_dir}/rebase-merge/end" total if [[ -f "${git_dir}/rebase-merge/interactive" ]]; then state="|REBASE-i" else state="|REBASE-m" fi else if [[ -d "${git_dir}/rebase-apply" ]]; then __git_prompt_read "${git_dir}/rebase-apply/next" step __git_prompt_read "${git_dir}/rebase-apply/last" total if [[ -f "${git_dir}/rebase-apply/rebasing" ]]; then state="|REBASE" elif [[ -f "${git_dir}/rebase-apply/applying" ]]; then state="|AM" else state="|AM/REBASE" fi elif [[ -f "${git_dir}/MERGE_HEAD" ]]; then state="|MERGING" elif [[ -f "${git_dir}/CHERRY_PICK_HEAD" ]]; then state="|CHERRY-PICKING" elif [[ -f "${git_dir}/REVERT_HEAD" ]]; then state="|REVERTING" elif [[ -f "${git_dir}/BISECT_LOG" ]]; then state="|BISECTING" fi fi if [[ -n "${step}" ]] && [[ -n "${total}" ]]; then state="${state} ${step}/${total}" fi num_staged=0 num_changed=0 num_conflicts=0 num_untracked=0 while IFS='' read -r line || [[ -n "${line}" ]]; do status="${line:0:2}" while [[ -n ${status} ]]; do case "${status}" in #two fixed character matches, loop finished \#\#) branch_line="${line/\.\.\./^}"; break ;; \?\?) ((num_untracked++)); break ;; U?) ((num_conflicts++)); break;; ?U) ((num_conflicts++)); break;; DD) ((num_conflicts++)); break;; AA) ((num_conflicts++)); break;; #two character matches, first loop ?M) ((num_changed++)) ;; ?\ ) ;; #single character matches, second loop U) ((num_conflicts++)) ;; \ ) ;; *) ((num_staged++)) ;; esac status="${status:0:(${#status}-1)}" done done <<< "${gitstatus}" num_stashed=0 if [[ "${__GIT_PROMPT_IGNORE_STASH:-0}" != "1" ]]; then stash_file="${git_dir}/logs/refs/stash" if [[ -e "${stash_file}" ]]; then while IFS='' read -r wcline || [[ -n "${wcline}" ]]; do ((num_stashed++)) done < "${stash_file}" fi fi clean=0 if (( num_changed == 0 && num_staged == 0 && num_untracked == 0 && num_stashed == 0 && num_conflicts == 0)) ; then clean=1 fi IFS="^" read -ra branch_fields <<< "${branch_line/\#\# }" branch="${branch_fields[@]:0:1}" remote="" upstream="" detached_head=0 if [[ "${branch}" == *"Initial commit on"* ]]; then IFS=" " read -ra fields <<< "${branch}" branch="${fields[@]:3:1}" remote="_NO_REMOTE_TRACKING_" remote_url='.' elif [[ "${branch}" == *"No commits yet on"* ]]; then IFS=" " read -ra fields <<< "${branch}" branch="${fields[@]:4:1}" remote="_NO_REMOTE_TRACKING_" remote_url='.' elif [[ "${branch}" == *"no branch"* ]]; then tag=$( git describe --tags --exact-match ) if [[ -n "${tag}" ]]; then branch="_PRETAG_${tag}" detached_head=1 else branch="_PREHASH_$( git rev-parse --short HEAD )" detached_head=1 fi else if [[ "${#branch_fields[@]}" -eq 1 ]]; then remote="_NO_REMOTE_TRACKING_" remote_url='.' else IFS="[,]" read -ra remote_fields <<< "${branch_fields[1]}" upstream="${remote_fields[@]:0:1}" for remote_field in "${remote_fields[@]}"; do if [[ "${remote_field}" == "ahead "* ]]; then num_ahead="${remote_field:6}" ahead="_AHEAD_${num_ahead}" fi if [[ "${remote_field}" == "behind "* ]] || [[ "${remote_field}" == " behind "* ]]; then num_behind="${remote_field:7}" behind="_BEHIND_${num_behind# }" fi done remote="${behind-}${ahead-}" fi fi if [[ -z "${remote:+x}" ]] ; then remote='.' fi if [[ -z "${upstream:+x}" ]] ; then upstream='^' fi UPSTREAM_TRIMMED=$(echo $upstream | xargs) printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n" \ "${branch}${state}" \ "${remote}" \ "${remote_url}" \ "${UPSTREAM_TRIMMED}" \ "${num_staged}" \ "${num_conflicts}" \ "${num_changed}" \ "${num_untracked}" \ "${num_stashed}" \ "${clean}" \ "${detached_head}" exit