Git 2.23
[gitweb.git] / contrib / completion / git-prompt.sh
index dba928ed00458cae9987e2bdbbad324827435ad3..1d510cd47bef49d918704a45e9592e0ffcb7ee0f 100644 (file)
 #     git           always compare HEAD to @{upstream}
 #     svn           always compare HEAD to your SVN upstream
 #
+# You can change the separator between the branch name and the above
+# state symbols by setting GIT_PS1_STATESEPARATOR. The default separator
+# is SP.
+#
 # By default, __git_ps1 will compare HEAD to your SVN upstream if it can
 # find one, or @{upstream} otherwise.  Once you have set
 # GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
 #     contains      relative to newer annotated tag (v1.6.3.2~35)
 #     branch        relative to newer tag or branch (master~4)
 #     describe      relative to older annotated tag (v1.6.3.1-13-gdd42c2f)
+#     tag           relative to any older tag (v1.6.3.1-13-gdd42c2f)
 #     default       exactly matching tag
 #
 # If you would like a colored hint about the current dirty state, set
 # GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
 # the colored output of "git status -sb" and are available only when
 # using __git_ps1 for PROMPT_COMMAND or precmd.
+#
+# If you would like __git_ps1 to do nothing in the case when the current
+# directory is set up to be ignored by git, then set
+# GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
+# repository level by setting bash.hideIfPwdIgnored to "false".
 
 # check whether printf supports -v
 __git_printf_supports_v=
@@ -207,7 +217,16 @@ __git_ps1_show_upstream ()
                        p=" u+${count#* }-${count%      *}" ;;
                esac
                if [[ -n "$count" && -n "$name" ]]; then
-                       p="$p $(git rev-parse --abbrev-ref "$upstream" 2>/dev/null)"
+                       __git_ps1_upstream_name=$(git rev-parse \
+                               --abbrev-ref "$upstream" 2>/dev/null)
+                       if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
+                               p="$p \${__git_ps1_upstream_name}"
+                       else
+                               p="$p ${__git_ps1_upstream_name}"
+                               # not needed anymore; keep user's
+                               # environment clean
+                               unset __git_ps1_upstream_name
+                       fi
                fi
        fi
 
@@ -259,11 +278,43 @@ __git_ps1_colorize_gitstring ()
        r="$c_clear$r"
 }
 
-eread ()
+# Helper function to read the first line of a file into a variable.
+# __git_eread requires 2 arguments, the file path and the name of the
+# variable, in that order.
+__git_eread ()
+{
+       test -r "$1" && IFS=$'\r\n' read "$2" <"$1"
+}
+
+# see if a cherry-pick or revert is in progress, if the user has committed a
+# conflict resolution with 'git commit' in the middle of a sequence of picks or
+# reverts then CHERRY_PICK_HEAD/REVERT_HEAD will not exist so we have to read
+# the todo file.
+__git_sequencer_status ()
 {
-       local f="$1"
-       shift
-       test -r "$f" && read "$@" <"$f"
+       local todo
+       if test -f "$g/CHERRY_PICK_HEAD"
+       then
+               r="|CHERRY-PICKING"
+               return 0;
+       elif test -f "$g/REVERT_HEAD"
+       then
+               r="|REVERTING"
+               return 0;
+       elif __git_eread "$g/sequencer/todo" todo
+       then
+               case "$todo" in
+               p[\ \   ]|pick[\ \      ]*)
+                       r="|CHERRY-PICKING"
+                       return 0
+               ;;
+               revert[\ \      ]*)
+                       r="|REVERTING"
+                       return 0
+               ;;
+               esac
+       fi
+       return 1
 }
 
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
@@ -279,6 +330,8 @@ eread ()
 # In this mode you can request colored hints using GIT_PS1_SHOWCOLORHINTS=true
 __git_ps1 ()
 {
+       # preserve exit status
+       local exit=$?
        local pcmode=no
        local detached=no
        local ps1pc_start='\u@\h:\w '
@@ -290,13 +343,54 @@ __git_ps1 ()
                        ps1pc_start="$1"
                        ps1pc_end="$2"
                        printf_format="${3:-$printf_format}"
+                       # set PS1 to a plain prompt so that we can
+                       # simply return early if the prompt should not
+                       # be decorated
+                       PS1="$ps1pc_start$ps1pc_end"
                ;;
                0|1)    printf_format="${1:-$printf_format}"
                ;;
-               *)      return
+               *)      return $exit
                ;;
        esac
 
+       # ps1_expanded:  This variable is set to 'yes' if the shell
+       # subjects the value of PS1 to parameter expansion:
+       #
+       #   * bash does unless the promptvars option is disabled
+       #   * zsh does not unless the PROMPT_SUBST option is set
+       #   * POSIX shells always do
+       #
+       # If the shell would expand the contents of PS1 when drawing
+       # the prompt, a raw ref name must not be included in PS1.
+       # This protects the user from arbitrary code execution via
+       # specially crafted ref names.  For example, a ref named
+       # 'refs/heads/$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' might cause the
+       # shell to execute 'sudo rm -rf /' when the prompt is drawn.
+       #
+       # Instead, the ref name should be placed in a separate global
+       # variable (in the __git_ps1_* namespace to avoid colliding
+       # with the user's environment) and that variable should be
+       # referenced from PS1.  For example:
+       #
+       #     __git_ps1_foo=$(do_something_to_get_ref_name)
+       #     PS1="...stuff...\${__git_ps1_foo}...stuff..."
+       #
+       # If the shell does not expand the contents of PS1, the raw
+       # ref name must be included in PS1.
+       #
+       # The value of this variable is only relevant when in pcmode.
+       #
+       # Assume that the shell follows the POSIX specification and
+       # expands PS1 unless determined otherwise.  (This is more
+       # likely to be correct if the user has a non-bash, non-zsh
+       # shell and safer than the alternative if the assumption is
+       # incorrect.)
+       #
+       local ps1_expanded=yes
+       [ -z "${ZSH_VERSION-}" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
+       [ -z "${BASH_VERSION-}" ] || shopt -q promptvars || ps1_expanded=no
+
        local repo_info rev_parse_exit_code
        repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
                --is-bare-repository --is-inside-work-tree \
@@ -304,14 +398,10 @@ __git_ps1 ()
        rev_parse_exit_code="$?"
 
        if [ -z "$repo_info" ]; then
-               if [ $pcmode = yes ]; then
-                       #In PC mode PS1 always needs to be set
-                       PS1="$ps1pc_start$ps1pc_end"
-               fi
-               return
+               return $exit
        fi
 
-       local short_sha
+       local short_sha=""
        if [ "$rev_parse_exit_code" = "0" ]; then
                short_sha="${repo_info##*$'\n'}"
                repo_info="${repo_info%$'\n'*}"
@@ -323,14 +413,22 @@ __git_ps1 ()
        local inside_gitdir="${repo_info##*$'\n'}"
        local g="${repo_info%$'\n'*}"
 
+       if [ "true" = "$inside_worktree" ] &&
+          [ -n "${GIT_PS1_HIDE_IF_PWD_IGNORED-}" ] &&
+          [ "$(git config --bool bash.hideIfPwdIgnored)" != "false" ] &&
+          git check-ignore -q .
+       then
+               return $exit
+       fi
+
        local r=""
        local b=""
        local step=""
        local total=""
        if [ -d "$g/rebase-merge" ]; then
-               eread "$g/rebase-merge/head-name" b
-               eread "$g/rebase-merge/msgnum" step
-               eread "$g/rebase-merge/end" total
+               __git_eread "$g/rebase-merge/head-name" b
+               __git_eread "$g/rebase-merge/msgnum" step
+               __git_eread "$g/rebase-merge/end" total
                if [ -f "$g/rebase-merge/interactive" ]; then
                        r="|REBASE-i"
                else
@@ -338,10 +436,10 @@ __git_ps1 ()
                fi
        else
                if [ -d "$g/rebase-apply" ]; then
-                       eread "$g/rebase-apply/next" step
-                       eread "$g/rebase-apply/last" total
+                       __git_eread "$g/rebase-apply/next" step
+                       __git_eread "$g/rebase-apply/last" total
                        if [ -f "$g/rebase-apply/rebasing" ]; then
-                               eread "$g/rebase-apply/head-name" b
+                               __git_eread "$g/rebase-apply/head-name" b
                                r="|REBASE"
                        elif [ -f "$g/rebase-apply/applying" ]; then
                                r="|AM"
@@ -350,10 +448,8 @@ __git_ps1 ()
                        fi
                elif [ -f "$g/MERGE_HEAD" ]; then
                        r="|MERGING"
-               elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
-                       r="|CHERRY-PICKING"
-               elif [ -f "$g/REVERT_HEAD" ]; then
-                       r="|REVERTING"
+               elif __git_sequencer_status; then
+                       :
                elif [ -f "$g/BISECT_LOG" ]; then
                        r="|BISECTING"
                fi
@@ -365,11 +461,8 @@ __git_ps1 ()
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
                        local head=""
-                       if ! eread "$g/HEAD" head; then
-                               if [ $pcmode = yes ]; then
-                                       PS1="$ps1pc_start$ps1pc_end"
-                               fi
-                               return
+                       if ! __git_eread "$g/HEAD" head; then
+                               return $exit
                        fi
                        # is it a symbolic ref?
                        b="${head#ref: }"
@@ -381,6 +474,8 @@ __git_ps1 ()
                                        git describe --contains HEAD ;;
                                (branch)
                                        git describe --contains --all HEAD ;;
+                               (tag)
+                                       git describe --tags HEAD ;;
                                (describe)
                                        git describe HEAD ;;
                                (* | default)
@@ -414,21 +509,21 @@ __git_ps1 ()
                if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
                   [ "$(git config --bool bash.showDirtyState)" != "false" ]
                then
-                       git diff --no-ext-diff --quiet --exit-code || w="*"
-                       if [ -n "$short_sha" ]; then
-                               git diff-index --cached --quiet HEAD -- || i="+"
-                       else
+                       git diff --no-ext-diff --quiet || w="*"
+                       git diff --no-ext-diff --cached --quiet || i="+"
+                       if [ -z "$short_sha" ] && [ -z "$i" ]; then
                                i="#"
                        fi
                fi
                if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
-                  [ -r "$g/refs/stash" ]; then
+                  git rev-parse --verify --quiet refs/stash >/dev/null
+               then
                        s="$"
                fi
 
                if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
                   [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
-                  git ls-files --others --exclude-standard --error-unmatch -- '*' >/dev/null 2>/dev/null
+                  git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' >/dev/null 2>/dev/null
                then
                        u="%${ZSH_VERSION+%}"
                fi
@@ -445,8 +540,14 @@ __git_ps1 ()
                __git_ps1_colorize_gitstring
        fi
 
+       b=${b##refs/heads/}
+       if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
+               __git_ps1_branch_name=$b
+               b="\${__git_ps1_branch_name}"
+       fi
+
        local f="$w$i$s$u"
-       local gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
+       local gitstring="$c$b${f:+$z$f}$r$p"
 
        if [ $pcmode = yes ]; then
                if [ "${__git_printf_supports_v-}" != yes ]; then
@@ -458,4 +559,6 @@ __git_ps1 ()
        else
                printf -- "$printf_format" "$gitstring"
        fi
+
+       return $exit
 }