Merge branch 'sg/bash-prompt'
authorJunio C Hamano <gitster@pobox.com>
Mon, 1 Jul 2013 19:41:37 +0000 (12:41 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 1 Jul 2013 19:41:37 +0000 (12:41 -0700)
* sg/bash-prompt:
bash prompt: mention that PROMPT_COMMAND mode is faster
bash prompt: avoid command substitution when finalizing gitstring
bash prompt: avoid command substitution when checking for untracked files
bash prompt: use bash builtins to check stash state
bash prompt: use bash builtins to check for unborn branch for dirty state
bash prompt: combine 'git rev-parse' for detached head
bash prompt: combine 'git rev-parse' executions in the main code path
bash prompt: use bash builtins to find out current branch
bash prompt: use bash builtins to find out rebase state
bash prompt: run 'git rev-parse --git-dir' directly instead of __gitdir()
bash prompt: return early from __git_ps1() when not in a git repository
bash prompt: print unique detached HEAD abbreviated object name
bash prompt: add a test for symbolic link symbolic refs
completion, bash prompt: move __gitdir() tests to completion test suite
bash prompt: use 'write_script' helper in interactive rebase test
bash prompt: fix redirection coding style in tests

contrib/completion/git-completion.bash
contrib/completion/git-prompt.sh
t/t9902-completion.sh
t/t9903-bash-prompt.sh
index 6c3bafeea569601c4b55de165eb7f56ac3be1d31..ebc40d4845586bcdd7df8f02c34db9b157cfe149 100644 (file)
@@ -33,8 +33,6 @@ esac
 # returns location of .git repo
 __gitdir ()
 {
-       # Note: this function is duplicated in git-prompt.sh
-       # When updating it, make sure you update the other one to match.
        if [ -z "${1-}" ]; then
                if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
index 07a6218d10679f5102c3649794bfd98e1883da75..daed6a1d039cc8b2f5adb1d089e36ea7011e0536 100644 (file)
 #        Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
 #        ZSH:  PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
 #        the optional argument will be used as format string.
-#    3b) Alternatively, if you are using bash, __git_ps1 can be
-#        used for PROMPT_COMMAND with two parameters, <pre> and
-#        <post>, which are strings you would put in $PS1 before
-#        and after the status string generated by the git-prompt
-#        machinery.  e.g.
+#    3b) Alternatively, for a slighly faster prompt, if you are
+#        using bash, __git_ps1 can be used for PROMPT_COMMAND
+#        with two parameters, <pre> and <post>, which are strings
+#        you would put in $PS1 before and after the status string
+#        generated by the git-prompt machinery.  e.g.
 #        Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
 #        ZSH:  precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
 #        will show username, at-sign, host, colon, cwd, then
 # GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on
 # the colored output of "git status -sb".
 
-# __gitdir accepts 0 or 1 arguments (i.e., location)
-# returns location of .git repo
-__gitdir ()
-{
-       # Note: this function is duplicated in git-completion.bash
-       # When updating it, make sure you update the other one to match.
-       if [ -z "${1-}" ]; then
-               if [ -n "${__git_dir-}" ]; then
-                       echo "$__git_dir"
-               elif [ -n "${GIT_DIR-}" ]; then
-                       test -d "${GIT_DIR-}" || return 1
-                       echo "$GIT_DIR"
-               elif [ -d .git ]; then
-                       echo .git
-               else
-                       git rev-parse --git-dir 2>/dev/null
-               fi
-       elif [ -d "$1/.git" ]; then
-               echo "$1/.git"
-       else
-               echo "$1"
-       fi
-}
-
 # stores the divergence from upstream in $p
 # used by GIT_PS1_SHOWUPSTREAM
 __git_ps1_show_upstream ()
@@ -335,50 +311,83 @@ __git_ps1 ()
                ;;
        esac
 
-       local g="$(__gitdir)"
-       if [ -z "$g" ]; then
+       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 \
+               --short HEAD 2>/dev/null)"
+       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
+       fi
+
+       local short_sha
+       if [ "$rev_parse_exit_code" = "0" ]; then
+               short_sha="${repo_info##*$'\n'}"
+               repo_info="${repo_info%$'\n'*}"
+       fi
+       local inside_worktree="${repo_info##*$'\n'}"
+       repo_info="${repo_info%$'\n'*}"
+       local bare_repo="${repo_info##*$'\n'}"
+       repo_info="${repo_info%$'\n'*}"
+       local inside_gitdir="${repo_info##*$'\n'}"
+       local g="${repo_info%$'\n'*}"
+
+       local r=""
+       local b=""
+       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"
+               if [ -f "$g/rebase-merge/interactive" ]; then
+                       r="|REBASE-i"
+               else
+                       r="|REBASE-m"
+               fi
        else
-               local r=""
-               local b=""
-               local step=""
-               local total=""
-               if [ -d "$g/rebase-merge" ]; then
-                       b="$(cat "$g/rebase-merge/head-name" 2>/dev/null)"
-                       step=$(cat "$g/rebase-merge/msgnum" 2>/dev/null)
-                       total=$(cat "$g/rebase-merge/end" 2>/dev/null)
-                       if [ -f "$g/rebase-merge/interactive" ]; then
-                               r="|REBASE-i"
+               if [ -d "$g/rebase-apply" ]; then
+                       read step 2>/dev/null <"$g/rebase-apply/next"
+                       read total 2>/dev/null <"$g/rebase-apply/last"
+                       if [ -f "$g/rebase-apply/rebasing" ]; then
+                               read b 2>/dev/null <"$g/rebase-apply/head-name"
+                               r="|REBASE"
+                       elif [ -f "$g/rebase-apply/applying" ]; then
+                               r="|AM"
                        else
-                               r="|REBASE-m"
+                               r="|AM/REBASE"
                        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 [ -f "$g/BISECT_LOG" ]; then
+                       r="|BISECTING"
+               fi
+
+               if [ -n "$b" ]; then
+                       :
+               elif [ -h "$g/HEAD" ]; then
+                       # symlink symbolic ref
+                       b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
-                       if [ -d "$g/rebase-apply" ]; then
-                               step=$(cat "$g/rebase-apply/next" 2>/dev/null)
-                               total=$(cat "$g/rebase-apply/last" 2>/dev/null)
-                               if [ -f "$g/rebase-apply/rebasing" ]; then
-                                       b="$(cat "$g/rebase-apply/head-name" 2>/dev/null)"
-                                       r="|REBASE"
-                               elif [ -f "$g/rebase-apply/applying" ]; then
-                                       r="|AM"
-                               else
-                                       r="|AM/REBASE"
+                       local head=""
+                       if ! read head 2>/dev/null <"$g/HEAD"; then
+                               if [ $pcmode = yes ]; then
+                                       PS1="$ps1pc_start$ps1pc_end"
                                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 [ -f "$g/BISECT_LOG" ]; then
-                               r="|BISECTING"
+                               return
                        fi
-
-                       test -n "$b" ||
-                       b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+                       # is it a symbolic ref?
+                       b="${head#ref: }"
+                       if [ "$head" = "$b" ]; then
                                detached=yes
                                b="$(
                                case "${GIT_PS1_DESCRIBE_STYLE-}" in
@@ -392,70 +401,74 @@ __git_ps1 ()
                                        git describe --tags --exact-match HEAD ;;
                                esac 2>/dev/null)" ||
 
-                               b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
-                               b="unknown"
+                               b="$short_sha..."
                                b="($b)"
-                       }
+                       fi
                fi
+       fi
 
-               if [ -n "$step" ] && [ -n "$total" ]; then
-                       r="$r $step/$total"
-               fi
+       if [ -n "$step" ] && [ -n "$total" ]; then
+               r="$r $step/$total"
+       fi
 
-               local w=""
-               local i=""
-               local s=""
-               local u=""
-               local c=""
-               local p=""
+       local w=""
+       local i=""
+       local s=""
+       local u=""
+       local c=""
+       local p=""
 
-               if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
-                       if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
-                               c="BARE:"
+       if [ "true" = "$inside_gitdir" ]; then
+               if [ "true" = "$bare_repo" ]; then
+                       c="BARE:"
+               else
+                       b="GIT_DIR!"
+               fi
+       elif [ "true" = "$inside_worktree" ]; then
+               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
-                               b="GIT_DIR!"
-                       fi
-               elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
-                       if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] &&
-                          [ "$(git config --bool bash.showDirtyState)" != "false" ]
-                       then
-                               git diff --no-ext-diff --quiet --exit-code || w="*"
-                               if git rev-parse --quiet --verify HEAD >/dev/null; then
-                                       git diff-index --cached --quiet HEAD -- || i="+"
-                               else
-                                       i="#"
-                               fi
-                       fi
-                       if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
-                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+                               i="#"
                        fi
+               fi
+               if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ] &&
+                  [ -r "$g/refs/stash" ]; then
+                       s="$"
+               fi
 
-                       if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ] &&
-                          [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] &&
-                          [ -n "$(git ls-files --others --exclude-standard)" ]
-                       then
-                               u="%${ZSH_VERSION+%}"
-                       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
+               then
+                       u="%${ZSH_VERSION+%}"
+               fi
 
-                       if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
-                               __git_ps1_show_upstream
-                       fi
+               if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+                       __git_ps1_show_upstream
                fi
+       fi
 
-               local z="${GIT_PS1_STATESEPARATOR-" "}"
-               local f="$w$i$s$u"
-               if [ $pcmode = yes ]; then
-                       local gitstring=
-                       if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
-                               __git_ps1_colorize_gitstring
-                       else
-                               gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
-                       fi
+       local z="${GIT_PS1_STATESEPARATOR-" "}"
+       local f="$w$i$s$u"
+       if [ $pcmode = yes ]; then
+               local gitstring=
+               if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
+                       __git_ps1_colorize_gitstring
+               else
+                       gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
+               fi
+               if [[ -n ${ZSH_VERSION-} ]]; then
                        gitstring=$(printf -- "$printf_format" "$gitstring")
-                       PS1="$ps1pc_start$gitstring$ps1pc_end"
                else
-                       # NO color option unless in PROMPT_COMMAND mode
-                       printf -- "$printf_format" "$c${b##refs/heads/}${f:+$z$f}$r$p"
+                       printf -v gitstring -- "$printf_format" "$gitstring"
                fi
+               PS1="$ps1pc_start$gitstring$ps1pc_end"
+       else
+               # NO color option unless in PROMPT_COMMAND mode
+               printf -- "$printf_format" "$c${b##refs/heads/}${f:+$z$f}$r$p"
        fi
 }
index 81a1657efb18e88a448886aed60bb7e5fdb36212..5469dee8d1f39e6cae4205501a774d36913fff73 100755 (executable)
@@ -122,6 +122,140 @@ test_gitcomp_nl ()
 
 invalid_variable_name='${foo.bar}'
 
+actual="$TRASH_DIRECTORY/actual"
+
+test_expect_success 'setup for __gitdir tests' '
+       mkdir -p subdir/subsubdir &&
+       git init otherrepo
+'
+
+test_expect_success '__gitdir - from command line (through $__git_dir)' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+       (
+               __git_dir="$TRASH_DIRECTORY/otherrepo/.git" &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - repo as argument' '
+       echo "otherrepo/.git" >expected &&
+       __gitdir "otherrepo" >"$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - remote as argument' '
+       echo "remote" >expected &&
+       __gitdir "remote" >"$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - .git directory in cwd' '
+       echo ".git" >expected &&
+       __gitdir >"$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - .git directory in parent' '
+       echo "$(pwd -P)/.git" >expected &&
+       (
+               cd subdir/subsubdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - cwd is a .git directory' '
+       echo "." >expected &&
+       (
+               cd .git &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - parent is a .git directory' '
+       echo "$(pwd -P)/.git" >expected &&
+       (
+               cd .git/refs/heads &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - $GIT_DIR set while .git directory in cwd' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+       (
+               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               export GIT_DIR &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+       (
+               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               export GIT_DIR &&
+               cd subdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - non-existing $GIT_DIR' '
+       (
+               GIT_DIR="$TRASH_DIRECTORY/non-existing" &&
+               export GIT_DIR &&
+               test_must_fail __gitdir
+       )
+'
+
+test_expect_success '__gitdir - gitfile in cwd' '
+       echo "$(pwd -P)/otherrepo/.git" >expected &&
+       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git &&
+       test_when_finished "rm -f subdir/.git" &&
+       (
+               cd subdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - gitfile in parent' '
+       echo "$(pwd -P)/otherrepo/.git" >expected &&
+       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git &&
+       test_when_finished "rm -f subdir/.git" &&
+       (
+               cd subdir/subsubdir &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' '
+       echo "$(pwd -P)/otherrepo/.git" >expected &&
+       mkdir otherrepo/dir &&
+       test_when_finished "rm -rf otherrepo/dir" &&
+       ln -s otherrepo/dir link &&
+       test_when_finished "rm -f link" &&
+       (
+               cd link &&
+               __gitdir >"$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - not a git repository' '
+       (
+               cd subdir/subsubdir &&
+               GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" &&
+               export GIT_CEILING_DIRECTORIES &&
+               test_must_fail __gitdir
+       )
+'
+
 test_expect_success '__gitcomp - trailing space - options' '
        test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message=
                --reset-author" <<-EOF
index 15521cc4f9be68ccd105916cee1322041682e8d5..c05458cbe671b4881af3596d9b06a120674519d7 100755 (executable)
@@ -12,526 +12,415 @@ test_description='test git-specific bash prompt functions'
 actual="$TRASH_DIRECTORY/actual"
 
 test_expect_success 'setup for prompt tests' '
-       mkdir -p subdir/subsubdir &&
        git init otherrepo &&
-       echo 1 > file &&
+       echo 1 >file &&
        git add file &&
        test_tick &&
        git commit -m initial &&
        git tag -a -m msg1 t1 &&
        git checkout -b b1 &&
-       echo 2 > file &&
+       echo 2 >file &&
        git commit -m "second b1" file &&
-       echo 3 > file &&
+       echo 3 >file &&
        git commit -m "third b1" file &&
        git tag -a -m msg2 t2 &&
        git checkout -b b2 master &&
-       echo 0 > file &&
+       echo 0 >file &&
        git commit -m "second b2" file &&
-       echo 00 > file &&
+       echo 00 >file &&
        git commit -m "another b2" file &&
-       echo 000 > file &&
+       echo 000 >file &&
        git commit -m "yet another b2" file &&
        git checkout master
 '
 
-test_expect_success 'gitdir - from command line (through $__git_dir)' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
-       (
-               __git_dir="$TRASH_DIRECTORY/otherrepo/.git" &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - repo as argument' '
-       echo "otherrepo/.git" > expected &&
-       __gitdir "otherrepo" > "$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - remote as argument' '
-       echo "remote" > expected &&
-       __gitdir "remote" > "$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - .git directory in cwd' '
-       echo ".git" > expected &&
-       __gitdir > "$actual" &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - .git directory in parent' '
-       echo "$(pwd -P)/.git" > expected &&
-       (
-               cd subdir/subsubdir &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - cwd is a .git directory' '
-       echo "." > expected &&
-       (
-               cd .git &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - parent is a .git directory' '
-       echo "$(pwd -P)/.git" > expected &&
-       (
-               cd .git/refs/heads &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - $GIT_DIR set while .git directory in cwd' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
-       (
-               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
-               export GIT_DIR &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - $GIT_DIR set while .git directory in parent' '
-       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
-       (
-               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
-               export GIT_DIR &&
-               cd subdir &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - non-existing $GIT_DIR' '
-       (
-               GIT_DIR="$TRASH_DIRECTORY/non-existing" &&
-               export GIT_DIR &&
-               test_must_fail __gitdir
-       )
-'
-
-test_expect_success 'gitdir - gitfile in cwd' '
-       echo "$(pwd -P)/otherrepo/.git" > expected &&
-       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
-       test_when_finished "rm -f subdir/.git" &&
-       (
-               cd subdir &&
-               __gitdir > "$actual"
-       ) &&
-       test_cmp expected "$actual"
-'
-
-test_expect_success 'gitdir - gitfile in parent' '
-       echo "$(pwd -P)/otherrepo/.git" > expected &&
-       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
-       test_when_finished "rm -f subdir/.git" &&
-       (
-               cd subdir/subsubdir &&
-               __gitdir > "$actual"
-       ) &&
+test_expect_success 'prompt - branch name' '
+       printf " (master)" >expected &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
-test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' '
-       echo "$(pwd -P)/otherrepo/.git" > expected &&
-       mkdir otherrepo/dir &&
-       test_when_finished "rm -rf otherrepo/dir" &&
-       ln -s otherrepo/dir link &&
-       test_when_finished "rm -f link" &&
-       (
-               cd link &&
-               __gitdir > "$actual"
-       ) &&
+test_expect_success SYMLINKS 'prompt - branch name - symlink symref' '
+       printf " (master)" >expected &&
+       test_when_finished "git checkout master" &&
+       test_config core.preferSymlinkRefs true &&
+       git checkout master &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
-test_expect_success 'gitdir - not a git repository' '
-       (
-               cd subdir/subsubdir &&
-               GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" &&
-               export GIT_CEILING_DIRECTORIES &&
-               test_must_fail __gitdir
-       )
-'
-
-test_expect_success 'prompt - branch name' '
-       printf " (master)" > expected &&
-       __git_ps1 > "$actual" &&
+test_expect_success 'prompt - unborn branch' '
+       printf " (unborn)" >expected &&
+       git checkout --orphan unborn &&
+       test_when_finished "git checkout master" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - detached head' '
-       printf " ((%s...))" $(git log -1 --format="%h" b1^) > expected &&
+       printf " ((%s...))" $(git log -1 --format="%h" --abbrev=13 b1^) >expected &&
+       test_config core.abbrev 13 &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - contains' '
-       printf " ((t2~1))" > expected &&
+       printf " ((t2~1))" >expected &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
        (
                GIT_PS1_DESCRIBE_STYLE=contains &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - branch' '
-       printf " ((b1~1))" > expected &&
+       printf " ((b1~1))" >expected &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
        (
                GIT_PS1_DESCRIBE_STYLE=branch &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - describe' '
-       printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) > expected &&
+       printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) >expected &&
        git checkout b1^ &&
        test_when_finished "git checkout master" &&
        (
                GIT_PS1_DESCRIBE_STYLE=describe &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - describe detached head - default' '
-       printf " ((t2))" > expected &&
+       printf " ((t2))" >expected &&
        git checkout --detach b1 &&
        test_when_finished "git checkout master" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        (
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - deep inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        (
                cd .git/refs/heads &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - inside bare repository' '
-       printf " (BARE:master)" > expected &&
+       printf " (BARE:master)" >expected &&
        git init --bare bare.git &&
        test_when_finished "rm -rf bare.git" &&
        (
                cd bare.git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - interactive rebase' '
-       printf " (b1|REBASE-i 2/3)" > expected
-       echo "#!$SHELL_PATH" >fake_editor.sh &&
-       cat >>fake_editor.sh <<\EOF &&
-echo "exec echo" > "$1"
-echo "edit $(git log -1 --format="%h")" >> "$1"
-echo "exec echo" >> "$1"
-EOF
+       printf " (b1|REBASE-i 2/3)" >expected
+       write_script fake_editor.sh <<-\EOF &&
+               echo "exec echo" >"$1"
+               echo "edit $(git log -1 --format="%h")" >>"$1"
+               echo "exec echo" >>"$1"
+       EOF
        test_when_finished "rm -f fake_editor.sh" &&
-       chmod a+x fake_editor.sh &&
        test_set_editor "$TRASH_DIRECTORY/fake_editor.sh" &&
        git checkout b1 &&
        test_when_finished "git checkout master" &&
        git rebase -i HEAD^ &&
        test_when_finished "git rebase --abort"
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - rebase merge' '
-       printf " (b2|REBASE-m 1/3)" > expected &&
+       printf " (b2|REBASE-m 1/3)" >expected &&
        git checkout b2 &&
        test_when_finished "git checkout master" &&
        test_must_fail git rebase --merge b1 b2 &&
        test_when_finished "git rebase --abort" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - rebase' '
-       printf " (b2|REBASE 1/3)" > expected &&
+       printf " (b2|REBASE 1/3)" >expected &&
        git checkout b2 &&
        test_when_finished "git checkout master" &&
        test_must_fail git rebase b1 b2 &&
        test_when_finished "git rebase --abort" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - merge' '
-       printf " (b1|MERGING)" > expected &&
+       printf " (b1|MERGING)" >expected &&
        git checkout b1 &&
        test_when_finished "git checkout master" &&
        test_must_fail git merge b2 &&
        test_when_finished "git reset --hard" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - cherry-pick' '
-       printf " (master|CHERRY-PICKING)" > expected &&
+       printf " (master|CHERRY-PICKING)" >expected &&
        test_must_fail git cherry-pick b1 &&
        test_when_finished "git reset --hard" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - bisect' '
-       printf " (master|BISECTING)" > expected &&
+       printf " (master|BISECTING)" >expected &&
        git bisect start &&
        test_when_finished "git bisect reset" &&
-       __git_ps1 > "$actual" &&
+       __git_ps1 >"$actual" &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - clean' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - dirty worktree' '
-       printf " (master *)" > expected &&
-       echo "dirty" > file &&
+       printf " (master *)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - dirty index' '
-       printf " (master +)" > expected &&
-       echo "dirty" > file &&
+       printf " (master +)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        git add -u &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - dirty index and worktree' '
-       printf " (master *+)" > expected &&
-       echo "dirty index" > file &&
+       printf " (master *+)" >expected &&
+       echo "dirty index" >file &&
        test_when_finished "git reset --hard" &&
        git add -u &&
-       echo "dirty worktree" > file &&
+       echo "dirty worktree" >file &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - before root commit' '
-       printf " (master #)" > expected &&
+       printf " (master #)" >expected &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
                cd otherrepo &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable unset with config disabled' '
-       printf " (master)" > expected &&
-       echo "dirty" > file &&
+       printf " (master)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState false &&
        (
                sane_unset GIT_PS1_SHOWDIRTYSTATE &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable unset with config enabled' '
-       printf " (master)" > expected &&
-       echo "dirty" > file &&
+       printf " (master)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState true &&
        (
                sane_unset GIT_PS1_SHOWDIRTYSTATE &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable set with config disabled' '
-       printf " (master)" > expected &&
-       echo "dirty" > file &&
+       printf " (master)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState false &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - shell variable set with config enabled' '
-       printf " (master *)" > expected &&
-       echo "dirty" > file &&
+       printf " (master *)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        test_config bash.showDirtyState true &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - dirty status indicator - not shown inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
-       echo "dirty" > file &&
+       printf " (GIT_DIR!)" >expected &&
+       echo "dirty" >file &&
        test_when_finished "git reset --hard" &&
        (
                GIT_PS1_SHOWDIRTYSTATE=y &&
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - stash status indicator - no stash' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        (
                GIT_PS1_SHOWSTASHSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - stash status indicator - stash' '
-       printf " (master $)" > expected &&
+       printf " (master $)" >expected &&
        echo 2 >file &&
        git stash &&
        test_when_finished "git stash drop" &&
+       git pack-refs --all &&
        (
                GIT_PS1_SHOWSTASHSTATE=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - stash status indicator - not shown inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        echo 2 >file &&
        git stash &&
        test_when_finished "git stash drop" &&
        (
                GIT_PS1_SHOWSTASHSTATE=y &&
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - no untracked files' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
                cd otherrepo &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - untracked files' '
-       printf " (master %%)" > expected &&
+       printf " (master %%)" >expected &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable unset with config disabled' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        test_config bash.showUntrackedFiles false &&
        (
                sane_unset GIT_PS1_SHOWUNTRACKEDFILES &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable unset with config enabled' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        test_config bash.showUntrackedFiles true &&
        (
                sane_unset GIT_PS1_SHOWUNTRACKEDFILES &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable set with config disabled' '
-       printf " (master)" > expected &&
+       printf " (master)" >expected &&
        test_config bash.showUntrackedFiles false &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - shell variable set with config enabled' '
-       printf " (master %%)" > expected &&
+       printf " (master %%)" >expected &&
        test_config bash.showUntrackedFiles true &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - untracked files status indicator - not shown inside .git directory' '
-       printf " (GIT_DIR!)" > expected &&
+       printf " (GIT_DIR!)" >expected &&
        (
                GIT_PS1_SHOWUNTRACKEDFILES=y &&
                cd .git &&
-               __git_ps1 > "$actual"
+               __git_ps1 >"$actual"
        ) &&
        test_cmp expected "$actual"
 '
 
 test_expect_success 'prompt - format string starting with dash' '
-       printf -- "-master" > expected &&
-       __git_ps1 "-%s" > "$actual" &&
+       printf -- "-master" >expected &&
+       __git_ps1 "-%s" >"$actual" &&
        test_cmp expected "$actual"
 '