git prompt: use toplevel to find untracked files
[gitweb.git] / contrib / completion / git-prompt.sh
index bd7ff291b2b62a5fba85f3c33bf26578729e1e04..10c4d0ad74bc072afc86440c8810e2c3faae00ba 100644 (file)
@@ -209,9 +209,7 @@ __git_ps1_show_upstream ()
                if [[ -n "$count" && -n "$name" ]]; then
                        __git_ps1_upstream_name=$(git rev-parse \
                                --abbrev-ref "$upstream" 2>/dev/null)
-                       if [ $pcmode = yes ]; then
-                               # see the comments around the
-                               # __git_ps1_branch_name variable below
+                       if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
                                p="$p \${__git_ps1_upstream_name}"
                        else
                                p="$p ${__git_ps1_upstream_name}"
@@ -270,6 +268,13 @@ __git_ps1_colorize_gitstring ()
        r="$c_clear$r"
 }
 
+__git_eread ()
+{
+       f="$1"
+       shift
+       test -r "$f" && read "$@" <"$f"
+}
+
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
 # when called from PS1 using command substitution
 # in this mode it prints text to add to bash PS1 prompt (includes branch name)
@@ -301,6 +306,43 @@ __git_ps1 ()
                ;;
        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 \
@@ -332,9 +374,9 @@ __git_ps1 ()
        local step=""
        local total=""
        if [ -d "$g/rebase-merge" ]; then
-               read b 2>/dev/null <"$g/rebase-merge/head-name"
-               read step 2>/dev/null <"$g/rebase-merge/msgnum"
-               read total 2>/dev/null <"$g/rebase-merge/end"
+               __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
@@ -342,10 +384,10 @@ __git_ps1 ()
                fi
        else
                if [ -d "$g/rebase-apply" ]; then
-                       read step 2>/dev/null <"$g/rebase-apply/next"
-                       read total 2>/dev/null <"$g/rebase-apply/last"
+                       __git_eread "$g/rebase-apply/next" step
+                       __git_eread "$g/rebase-apply/last" total
                        if [ -f "$g/rebase-apply/rebasing" ]; then
-                               read b 2>/dev/null <"$g/rebase-apply/head-name"
+                               __git_eread "$g/rebase-apply/head-name" b
                                r="|REBASE"
                        elif [ -f "$g/rebase-apply/applying" ]; then
                                r="|AM"
@@ -369,7 +411,7 @@ __git_ps1 ()
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
                        local head=""
-                       if ! read head 2>/dev/null <"$g/HEAD"; then
+                       if ! __git_eread "$g/HEAD" head; then
                                if [ $pcmode = yes ]; then
                                        PS1="$ps1pc_start$ps1pc_end"
                                fi
@@ -432,7 +474,7 @@ __git_ps1 ()
 
                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 --error-unmatch -- ':/*' >/dev/null 2>/dev/null
                then
                        u="%${ZSH_VERSION+%}"
                fi
@@ -450,21 +492,8 @@ __git_ps1 ()
        fi
 
        b=${b##refs/heads/}
-       if [ $pcmode = yes ]; then
-               # In pcmode (and only pcmode) the contents of
-               # $gitstring are subject to expansion by the shell.
-               # Avoid putting the raw ref name in the prompt to
-               # protect the user from arbitrary code execution via
-               # specially crafted ref names (e.g., a ref named
-               # '$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' would execute
-               # 'sudo rm -rf /' when the prompt is drawn).  Instead,
-               # put the ref name in a new global variable (in the
-               # __git_ps1_* namespace to avoid colliding with the
-               # user's environment) and reference that variable from
-               # PS1.
+       if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
                __git_ps1_branch_name=$b
-               # note that the $ is escaped -- the variable will be
-               # expanded later (when it's time to draw the prompt)
                b="\${__git_ps1_branch_name}"
        fi