gitstatus.sh 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env bash
  2. # -*- coding: utf-8 -*-
  3. # gitstatus.sh -- produce the current git repo status on STDOUT
  4. # Functionally equivalent to 'gitstatus.py', but written in bash (not python).
  5. #
  6. # Alan K. Stebbens <aks@stebbens.org> [http://github.com/aks]
  7. set -u
  8. if [[ -z "${__GIT_PROMPT_DIR:+x}" ]]; then
  9. SOURCE="${BASH_SOURCE[0]}"
  10. while [[ -h "${SOURCE}" ]]; do
  11. DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
  12. SOURCE="$(readlink "${SOURCE}")"
  13. [[ "${SOURCE}" != /* ]] && SOURCE="${DIR}/${SOURCE}"
  14. done
  15. __GIT_PROMPT_DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
  16. fi
  17. if [[ "${__GIT_PROMPT_IGNORE_SUBMODULES:-0}" == "1" ]]; then
  18. _ignore_submodules="--ignore-submodules"
  19. else
  20. _ignore_submodules=""
  21. fi
  22. if [[ "${__GIT_PROMPT_WITH_USERNAME_AND_REPO:-0}" == "1" ]]; then
  23. # returns "user/repo" from remote.origin.url git variable
  24. #
  25. # supports urls:
  26. # https://user@bitbucket.org/user/repo.git
  27. # https://github.com/user/repo.git
  28. # git@github.com:user/repo.git
  29. #
  30. remote_url=$(git config --get remote.origin.url | sed 's|^.*//||; s/.*@//; s/[^:/]\+[:/]//; s/.git$//')
  31. else
  32. remote_url='.'
  33. fi
  34. gitstatus=$( LC_ALL=C git status ${_ignore_submodules} --untracked-files="${__GIT_PROMPT_SHOW_UNTRACKED_FILES:-normal}" --porcelain --branch )
  35. # if the status is fatal, exit now
  36. [[ ! "${?}" ]] && exit 0
  37. git_dir="$(git rev-parse --git-dir 2>/dev/null)"
  38. [[ -z "${git_dir:+x}" ]] && exit 0
  39. __git_prompt_read ()
  40. {
  41. local f="${1}"
  42. shift
  43. [[ -r "${f}" ]] && read -r "${@}" <"${f}"
  44. }
  45. state=""
  46. step=""
  47. total=""
  48. if [[ -d "${git_dir}/rebase-merge" ]]; then
  49. __git_prompt_read "${git_dir}/rebase-merge/msgnum" step
  50. __git_prompt_read "${git_dir}/rebase-merge/end" total
  51. if [[ -f "${git_dir}/rebase-merge/interactive" ]]; then
  52. state="|REBASE-i"
  53. else
  54. state="|REBASE-m"
  55. fi
  56. else
  57. if [[ -d "${git_dir}/rebase-apply" ]]; then
  58. __git_prompt_read "${git_dir}/rebase-apply/next" step
  59. __git_prompt_read "${git_dir}/rebase-apply/last" total
  60. if [[ -f "${git_dir}/rebase-apply/rebasing" ]]; then
  61. state="|REBASE"
  62. elif [[ -f "${git_dir}/rebase-apply/applying" ]]; then
  63. state="|AM"
  64. else
  65. state="|AM/REBASE"
  66. fi
  67. elif [[ -f "${git_dir}/MERGE_HEAD" ]]; then
  68. state="|MERGING"
  69. elif [[ -f "${git_dir}/CHERRY_PICK_HEAD" ]]; then
  70. state="|CHERRY-PICKING"
  71. elif [[ -f "${git_dir}/REVERT_HEAD" ]]; then
  72. state="|REVERTING"
  73. elif [[ -f "${git_dir}/BISECT_LOG" ]]; then
  74. state="|BISECTING"
  75. fi
  76. fi
  77. if [[ -n "${step}" ]] && [[ -n "${total}" ]]; then
  78. state="${state} ${step}/${total}"
  79. fi
  80. num_staged=0
  81. num_changed=0
  82. num_conflicts=0
  83. num_untracked=0
  84. while IFS='' read -r line || [[ -n "${line}" ]]; do
  85. status="${line:0:2}"
  86. while [[ -n ${status} ]]; do
  87. case "${status}" in
  88. #two fixed character matches, loop finished
  89. \#\#) branch_line="${line/\.\.\./^}"; break ;;
  90. \?\?) ((num_untracked++)); break ;;
  91. U?) ((num_conflicts++)); break;;
  92. ?U) ((num_conflicts++)); break;;
  93. DD) ((num_conflicts++)); break;;
  94. AA) ((num_conflicts++)); break;;
  95. #two character matches, first loop
  96. ?M) ((num_changed++)) ;;
  97. ?D) ((num_changed++)) ;;
  98. ?\ ) ;;
  99. #single character matches, second loop
  100. U) ((num_conflicts++)) ;;
  101. \ ) ;;
  102. *) ((num_staged++)) ;;
  103. esac
  104. status="${status:0:(${#status}-1)}"
  105. done
  106. done <<< "${gitstatus}"
  107. num_stashed=0
  108. if [[ "${__GIT_PROMPT_IGNORE_STASH:-0}" != "1" ]]; then
  109. stash_file="${git_dir}/logs/refs/stash"
  110. if [[ -e "${stash_file}" ]]; then
  111. while IFS='' read -r wcline || [[ -n "${wcline}" ]]; do
  112. ((num_stashed++))
  113. done < "${stash_file}"
  114. fi
  115. fi
  116. clean=0
  117. if (( num_changed == 0 && num_staged == 0 && num_untracked == 0 && num_stashed == 0 && num_conflicts == 0)) ; then
  118. clean=1
  119. fi
  120. IFS="^" read -ra branch_fields <<< "${branch_line/\#\# }"
  121. branch="${branch_fields[0]}"
  122. remote=""
  123. upstream=""
  124. if [[ "${branch}" == *"Initial commit on"* ]]; then
  125. IFS=" " read -ra fields <<< "${branch}"
  126. branch="${fields[3]}"
  127. remote="_NO_REMOTE_TRACKING_"
  128. remote_url='.'
  129. elif [[ "${branch}" == *"No commits yet on"* ]]; then
  130. IFS=" " read -ra fields <<< "${branch}"
  131. branch="${fields[4]}"
  132. remote="_NO_REMOTE_TRACKING_"
  133. remote_url='.'
  134. elif [[ "${branch}" == *"no branch"* ]]; then
  135. tag=$( git describe --tags --exact-match )
  136. if [[ -n "${tag}" ]]; then
  137. branch="${tag}"
  138. else
  139. branch="_PREHASH_$( git rev-parse --short HEAD )"
  140. fi
  141. else
  142. if [[ "${#branch_fields[@]}" -eq 1 ]]; then
  143. remote="_NO_REMOTE_TRACKING_"
  144. remote_url='.'
  145. else
  146. IFS="[,]" read -ra remote_fields <<< "${branch_fields[1]}"
  147. upstream="${remote_fields[0]}"
  148. for remote_field in "${remote_fields[@]}"; do
  149. if [[ "${remote_field}" == "ahead "* ]]; then
  150. num_ahead="${remote_field:6}"
  151. ahead="_AHEAD_${num_ahead}"
  152. fi
  153. if [[ "${remote_field}" == "behind "* ]] || [[ "${remote_field}" == " behind "* ]]; then
  154. num_behind="${remote_field:7}"
  155. behind="_BEHIND_${num_behind# }"
  156. fi
  157. done
  158. remote="${behind-}${ahead-}"
  159. fi
  160. fi
  161. if [[ -z "${remote:+x}" ]] ; then
  162. remote='.'
  163. fi
  164. if [[ -z "${upstream:+x}" ]] ; then
  165. upstream='^'
  166. fi
  167. UPSTREAM_TRIMMED=`echo $upstream |xargs`
  168. printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n" \
  169. "${branch}${state}" \
  170. "${remote}" \
  171. "${remote_url}" \
  172. "${UPSTREAM_TRIMMED}" \
  173. "${num_staged}" \
  174. "${num_conflicts}" \
  175. "${num_changed}" \
  176. "${num_untracked}" \
  177. "${num_stashed}" \
  178. "${clean}"
  179. exit