regex: use regexec_buf()
[gitweb.git] / git-bisect.sh
index b2186a86279e8919214bb205400a05f32a316c3b..5d1cb00d86b3700b2012cae1ed3b3b8b744acaf2 100755 (executable)
@@ -1,51 +1,64 @@
 #!/bin/sh
 
-USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
+USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'
 LONG_USAGE='git bisect help
-        print this long help message.
-git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
-        reset bisect state and start bisection.
-git bisect bad [<rev>]
-        mark <rev> a known-bad revision.
-git bisect good [<rev>...]
-        mark <rev>... known-good revisions.
+       print this long help message.
+git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
+                [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
+       reset bisect state and start bisection.
+git bisect (bad|new) [<rev>]
+       mark <rev> a known-bad revision/
+               a revision after change in a given property.
+git bisect (good|old) [<rev>...]
+       mark <rev>... known-good revisions/
+               revisions before change in a given property.
+git bisect terms [--term-good | --term-bad]
+       show the terms used for old and new commits (default: bad, good)
 git bisect skip [(<rev>|<range>)...]
-        mark <rev>... untestable revisions.
+       mark <rev>... untestable revisions.
 git bisect next
-        find next bisection to test and check it out.
+       find next bisection to test and check it out.
 git bisect reset [<commit>]
-        finish bisection search and go back to commit.
+       finish bisection search and go back to commit.
 git bisect visualize
-        show bisect status in gitk.
+       show bisect status in gitk.
 git bisect replay <logfile>
-        replay bisection log.
+       replay bisection log.
 git bisect log
-        show bisect log.
+       show bisect log.
 git bisect run <cmd>...
-        use <cmd>... to automatically bisect.
+       use <cmd>... to automatically bisect.
 
 Please use "git help bisect" to get the full man page.'
 
 OPTIONS_SPEC=
 . git-sh-setup
 . git-sh-i18n
-require_work_tree
 
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+TERM_BAD=bad
+TERM_GOOD=good
+
+bisect_head()
+{
+       if test -f "$GIT_DIR/BISECT_HEAD"
+       then
+               echo BISECT_HEAD
+       else
+               echo HEAD
+       fi
+}
 
 bisect_autostart() {
        test -s "$GIT_DIR/BISECT_START" || {
-               (
-                       gettext "You need to start by \"git bisect start\"" &&
-                       echo
-               ) >&2
+               gettextln "You need to start by \"git bisect start\"" >&2
                if test -t 0
                then
                        # TRANSLATORS: Make sure to include [Y] and [n] in your
                        # translation. The program will only accept English input
                        # at this point.
-           gettext "Do you want me to do it for you [Y/n]? " >&2
+                       gettext "Do you want me to do it for you [Y/n]? " >&2
                        read yesno
                        case "$yesno" in
                        [Nn]*)
@@ -59,6 +72,81 @@ bisect_autostart() {
 }
 
 bisect_start() {
+       #
+       # Check for one bad and then some good revisions.
+       #
+       has_double_dash=0
+       for arg; do
+               case "$arg" in --) has_double_dash=1; break ;; esac
+       done
+       orig_args=$(git rev-parse --sq-quote "$@")
+       bad_seen=0
+       eval=''
+       must_write_terms=0
+       revs=''
+       if test "z$(git rev-parse --is-bare-repository)" != zfalse
+       then
+               mode=--no-checkout
+       else
+               mode=''
+       fi
+       while [ $# -gt 0 ]; do
+               arg="$1"
+               case "$arg" in
+               --)
+                       shift
+                       break
+               ;;
+               --no-checkout)
+                       mode=--no-checkout
+                       shift ;;
+               --term-good|--term-old)
+                       shift
+                       must_write_terms=1
+                       TERM_GOOD=$1
+                       shift ;;
+               --term-good=*|--term-old=*)
+                       must_write_terms=1
+                       TERM_GOOD=${1#*=}
+                       shift ;;
+               --term-bad|--term-new)
+                       shift
+                       must_write_terms=1
+                       TERM_BAD=$1
+                       shift ;;
+               --term-bad=*|--term-new=*)
+                       must_write_terms=1
+                       TERM_BAD=${1#*=}
+                       shift ;;
+               --*)
+                       die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
+               *)
+                       rev=$(git rev-parse -q --verify "$arg^{commit}") || {
+                               test $has_double_dash -eq 1 &&
+                               die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
+                               break
+                       }
+                       revs="$revs $rev"
+                       shift
+                       ;;
+               esac
+       done
+
+       for rev in $revs
+       do
+               # The user ran "git bisect start <sha1>
+               # <sha1>", hence did not explicitly specify
+               # the terms, but we are already starting to
+               # set references named with the default terms,
+               # and won't be able to change afterwards.
+               must_write_terms=1
+
+               case $bad_seen in
+               0) state=$TERM_BAD ; bad_seen=1 ;;
+               *) state=$TERM_GOOD ;;
+               esac
+               eval="$eval bisect_write '$state' '$rev' 'nolog' &&"
+       done
        #
        # Verify HEAD.
        #
@@ -74,7 +162,11 @@ bisect_start() {
        then
                # Reset to the rev from where we started.
                start_head=$(cat "$GIT_DIR/BISECT_START")
-               git checkout "$start_head" -- || exit
+               if test "z$mode" != "z--no-checkout"
+               then
+                       git checkout "$start_head" -- ||
+                       die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <valid-branch>'.")"
+               fi
        else
                # Get rev from where we start.
                case "$head" in
@@ -83,7 +175,7 @@ bisect_start() {
                        # cogito usage, and cogito users should understand
                        # it relates to cg-seek.
                        [ -s "$GIT_DIR/head-name" ] &&
-                               die "$(gettext "won't bisect on seeked tree")"
+                               die "$(gettext "won't bisect on cg-seek'ed tree")"
                        start_head="${head#refs/heads/}"
                        ;;
                *)
@@ -97,39 +189,6 @@ bisect_start() {
        #
        bisect_clean_state || exit
 
-       #
-       # Check for one bad and then some good revisions.
-       #
-       has_double_dash=0
-       for arg; do
-           case "$arg" in --) has_double_dash=1; break ;; esac
-       done
-       orig_args=$(git rev-parse --sq-quote "$@")
-       bad_seen=0
-       eval=''
-       while [ $# -gt 0 ]; do
-           arg="$1"
-           case "$arg" in
-           --)
-               shift
-               break
-               ;;
-           *)
-               rev=$(git rev-parse -q --verify "$arg^{commit}") || {
-                   test $has_double_dash -eq 1 &&
-                       die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
-                   break
-               }
-               case $bad_seen in
-               0) state='bad' ; bad_seen=1 ;;
-               *) state='good' ;;
-               esac
-               eval="$eval bisect_write '$state' '$rev' 'nolog'; "
-               shift
-               ;;
-           esac
-       done
-
        #
        # Change state.
        # In case of mistaken revs or checkout error, or signals received,
@@ -143,9 +202,16 @@ bisect_start() {
        #
        # Write new start state.
        #
-       echo "$start_head" >"$GIT_DIR/BISECT_START" &&
+       echo "$start_head" >"$GIT_DIR/BISECT_START" && {
+               test "z$mode" != "z--no-checkout" ||
+               git update-ref --no-deref BISECT_HEAD "$start_head"
+       } &&
        git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
-       eval "$eval" &&
+       eval "$eval true" &&
+       if test $must_write_terms -eq 1
+       then
+               write_terms "$TERM_BAD" "$TERM_GOOD"
+       fi &&
        echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
        #
        # Check if we can proceed to the next bisect state.
@@ -160,9 +226,12 @@ bisect_write() {
        rev="$2"
        nolog="$3"
        case "$state" in
-               bad)            tag="$state" ;;
-               good|skip)      tag="$state"-"$rev" ;;
-               *)              die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
+               "$TERM_BAD")
+                       tag="$state" ;;
+               "$TERM_GOOD"|skip)
+                       tag="$state"-"$rev" ;;
+               *)
+                       die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
        esac
        git update-ref "refs/bisect/$tag" "$rev" || exit
        echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
@@ -176,7 +245,8 @@ is_expected_rev() {
 
 check_expected_revs() {
        for _rev in "$@"; do
-               if ! is_expected_rev "$_rev"; then
+               if ! is_expected_rev "$_rev"
+               then
                        rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
                        rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
                        return
@@ -185,44 +255,48 @@ check_expected_revs() {
 }
 
 bisect_skip() {
-        all=''
+       all=''
        for arg in "$@"
        do
-           case "$arg" in
-            *..*)
-               revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
-            *)
-                revs=$(git rev-parse --sq-quote "$arg") ;;
-           esac
-            all="$all $revs"
-        done
-        eval bisect_state 'skip' $all
+               case "$arg" in
+               *..*)
+                       revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
+               *)
+                       revs=$(git rev-parse --sq-quote "$arg") ;;
+               esac
+               all="$all $revs"
+       done
+       eval bisect_state 'skip' $all
 }
 
 bisect_state() {
        bisect_autostart
        state=$1
+       check_and_set_terms $state
        case "$#,$state" in
        0,*)
                die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
-       1,bad|1,good|1,skip)
-               rev=$(git rev-parse --verify HEAD) ||
-                       die "$(gettext "Bad rev input: HEAD")"
+       1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip)
+               rev=$(git rev-parse --verify $(bisect_head)) ||
+                       die "$(gettext "Bad rev input: $(bisect_head)")"
                bisect_write "$state" "$rev"
                check_expected_revs "$rev" ;;
-       2,bad|*,good|*,skip)
+       2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip)
                shift
-               eval=''
+               hash_list=''
                for rev in "$@"
                do
                        sha=$(git rev-parse --verify "$rev^{commit}") ||
                                die "$(eval_gettext "Bad rev input: \$rev")"
-                       eval="$eval bisect_write '$state' '$sha'; "
+                       hash_list="$hash_list $sha"
+               done
+               for rev in $hash_list
+               do
+                       bisect_write "$state" "$rev"
                done
-               eval "$eval"
-               check_expected_revs "$@" ;;
-       *,bad)
-               die "$(gettext "'git bisect bad' can take only one argument.")" ;;
+               check_expected_revs $hash_list ;;
+       *,"$TERM_BAD")
+               die "$(eval_gettext "'git bisect \$TERM_BAD' can take only one argument.")" ;;
        *)
                usage ;;
        esac
@@ -231,24 +305,21 @@ bisect_state() {
 
 bisect_next_check() {
        missing_good= missing_bad=
-       git show-ref -q --verify refs/bisect/bad || missing_bad=t
-       test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
+       git show-ref -q --verify refs/bisect/$TERM_BAD || missing_bad=t
+       test -n "$(git for-each-ref "refs/bisect/$TERM_GOOD-*")" || missing_good=t
 
        case "$missing_good,$missing_bad,$1" in
        ,,*)
-               : have both good and bad - ok
+               : have both $TERM_GOOD and $TERM_BAD - ok
                ;;
        *,)
                # do not have both but not asked to fail - just report.
                false
                ;;
-       t,,good)
-               # have bad but not good.  we could bisect although
+       t,,"$TERM_GOOD")
+               # have bad (or new) but not good (or old).  we could bisect although
                # this is less optimum.
-               (
-                       gettext "Warning: bisecting only with a bad commit." &&
-                       echo
-               ) >&2
+               eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
                if test -t 0
                then
                        # TRANSLATORS: Make sure to include [Y] and [n] in your
@@ -258,24 +329,20 @@ bisect_next_check() {
                        read yesno
                        case "$yesno" in [Nn]*) exit 1 ;; esac
                fi
-               : bisect without good...
+               : bisect without $TERM_GOOD...
                ;;
        *)
-
+               bad_syn=$(bisect_voc bad)
+               good_syn=$(bisect_voc good)
                if test -s "$GIT_DIR/BISECT_START"
                then
-                       (
-                               gettext "You need to give me at least one good and one bad revisions.
-(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
-                               echo
-                       ) >&2
+
+                       eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision.
+(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
                else
-                       (
-                               gettext "You need to start by \"git bisect start\".
-You then need to give me at least one good and one bad revisions.
-(You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
-                               echo
-                       ) >&2
+                       eval_gettextln "You need to start by \"git bisect start\".
+You then need to give me at least one \$good_syn and one \$bad_syn revision.
+(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
                fi
                exit 1 ;;
        esac
@@ -288,14 +355,30 @@ bisect_auto_next() {
 bisect_next() {
        case "$#" in 0) ;; *) usage ;; esac
        bisect_autostart
-       bisect_next_check good
+       bisect_next_check $TERM_GOOD
 
        # Perform all bisection computation, display and checkout
-       git bisect--helper --next-all
+       git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
        res=$?
 
-        # Check if we should exit because bisection is finished
-       test $res -eq 10 && exit 0
+       # Check if we should exit because bisection is finished
+       if test $res -eq 10
+       then
+               bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD)
+               bad_commit=$(git show-branch $bad_rev)
+               echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
+               exit 0
+       elif test $res -eq 2
+       then
+               echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
+               good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*")
+               for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs)
+               do
+                       skipped_commit=$(git show-branch $skipped)
+                       echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
+               done
+               exit $res
+       fi
 
        # Check for an error in the bisection process
        test $res -ne 0 && exit $res
@@ -309,7 +392,8 @@ bisect_visualize() {
        if test $# = 0
        then
                if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
-                  type gitk >/dev/null 2>&1; then
+                       type gitk >/dev/null 2>&1
+               then
                        set gitk
                else
                        set git log
@@ -327,25 +411,26 @@ bisect_visualize() {
 
 bisect_reset() {
        test -s "$GIT_DIR/BISECT_START" || {
-               gettext "We are not bisecting."; echo
+               gettextln "We are not bisecting."
                return
        }
        case "$#" in
        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
-       1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
-              invalid="$1"
-              die "$(eval_gettext "'\$invalid' is not a valid commit")"
-          }
-          branch="$1" ;;
+       1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || {
+                       invalid="$1"
+                       die "$(eval_gettext "'\$invalid' is not a valid commit")"
+               }
+               branch="$1" ;;
        *)
-           usage ;;
+               usage ;;
        esac
-       if git checkout "$branch" -- ; then
-               bisect_clean_state
-       else
+
+       if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
+       then
                die "$(eval_gettext "Could not check out original HEAD '\$branch'.
 Try 'git bisect reset <commit>'.")"
        fi
+       bisect_clean_state
 }
 
 bisect_clean_state() {
@@ -360,9 +445,11 @@ bisect_clean_state() {
        rm -f "$GIT_DIR/BISECT_LOG" &&
        rm -f "$GIT_DIR/BISECT_NAMES" &&
        rm -f "$GIT_DIR/BISECT_RUN" &&
+       rm -f "$GIT_DIR/BISECT_TERMS" &&
        # Cleanup head-name if it got left by an old version of git-bisect
        rm -f "$GIT_DIR/head-name" &&
-
+       git update-ref -d --no-deref BISECT_HEAD &&
+       # clean up BISECT_START last
        rm -f "$GIT_DIR/BISECT_START"
 }
 
@@ -373,17 +460,22 @@ bisect_replay () {
        bisect_reset
        while read git bisect command rev
        do
-               test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
-               if test "$git" = "git-bisect"; then
+               test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue
+               if test "$git" = "git-bisect"
+               then
                        rev="$command"
                        command="$bisect"
                fi
+               get_terms
+               check_and_set_terms "$command"
                case "$command" in
                start)
                        cmd="bisect_start $rev"
                        eval "$cmd" ;;
-               good|bad|skip)
+               "$TERM_GOOD"|"$TERM_BAD"|skip)
                        bisect_write "$command" "$rev" ;;
+               terms)
+                       bisect_terms $rev ;;
                *)
                        die "$(gettext "?? what are you talking about?")" ;;
                esac
@@ -392,65 +484,62 @@ bisect_replay () {
 }
 
 bisect_run () {
-    bisect_next_check fail
-
-    while true
-    do
-      command="$@"
-      eval_gettext "running \$command"; echo
-      "$@"
-      res=$?
-
-      # Check for really bad run error.
-      if [ $res -lt 0 -o $res -ge 128 ]; then
-         (
-           eval_gettext "bisect run failed:
-exit code \$res from '\$command' is < 0 or >= 128" &&
-           echo
-         ) >&2
-         exit $res
-      fi
-
-      # Find current state depending on run success or failure.
-      # A special exit code of 125 means cannot test.
-      if [ $res -eq 125 ]; then
-         state='skip'
-      elif [ $res -gt 0 ]; then
-         state='bad'
-      else
-         state='good'
-      fi
-
-      # We have to use a subshell because "bisect_state" can exit.
-      ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
-      res=$?
-
-      cat "$GIT_DIR/BISECT_RUN"
-
-      if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
-               > /dev/null; then
-         (
-             gettext "bisect run cannot continue any more" &&
-             echo
-         ) >&2
-         exit $res
-      fi
-
-      if [ $res -ne 0 ]; then
-         (
-             eval_gettext "bisect run failed:
-'bisect_state \$state' exited with error code \$res" &&
-             echo
-         ) >&2
-         exit $res
-      fi
-
-      if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
-         gettext "bisect run success"; echo
-         exit 0;
-      fi
-
-    done
+       bisect_next_check fail
+
+       while true
+       do
+               command="$@"
+               eval_gettextln "running \$command"
+               "$@"
+               res=$?
+
+               # Check for really bad run error.
+               if [ $res -lt 0 -o $res -ge 128 ]
+               then
+                       eval_gettextln "bisect run failed:
+exit code \$res from '\$command' is < 0 or >= 128" >&2
+                       exit $res
+               fi
+
+               # Find current state depending on run success or failure.
+               # A special exit code of 125 means cannot test.
+               if [ $res -eq 125 ]
+               then
+                       state='skip'
+               elif [ $res -gt 0 ]
+               then
+                       state="$TERM_BAD"
+               else
+                       state="$TERM_GOOD"
+               fi
+
+               # We have to use a subshell because "bisect_state" can exit.
+               ( bisect_state $state >"$GIT_DIR/BISECT_RUN" )
+               res=$?
+
+               cat "$GIT_DIR/BISECT_RUN"
+
+               if sane_grep "first $TERM_BAD commit could be any of" "$GIT_DIR/BISECT_RUN" \
+                       >/dev/null
+               then
+                       gettextln "bisect run cannot continue any more" >&2
+                       exit $res
+               fi
+
+               if [ $res -ne 0 ]
+               then
+                       eval_gettextln "bisect run failed:
+'bisect_state \$state' exited with error code \$res" >&2
+                       exit $res
+               fi
+
+               if sane_grep "is the first $TERM_BAD commit" "$GIT_DIR/BISECT_RUN" >/dev/null
+               then
+                       gettextln "bisect run success"
+                       exit 0;
+               fi
+
+       done
 }
 
 bisect_log () {
@@ -458,35 +547,151 @@ bisect_log () {
        cat "$GIT_DIR/BISECT_LOG"
 }
 
+get_terms () {
+       if test -s "$GIT_DIR/BISECT_TERMS"
+       then
+               {
+               read TERM_BAD
+               read TERM_GOOD
+               } <"$GIT_DIR/BISECT_TERMS"
+       fi
+}
+
+write_terms () {
+       TERM_BAD=$1
+       TERM_GOOD=$2
+       if test "$TERM_BAD" = "$TERM_GOOD"
+       then
+               die "$(gettext "please use two different terms")"
+       fi
+       check_term_format "$TERM_BAD" bad
+       check_term_format "$TERM_GOOD" good
+       printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
+}
+
+check_term_format () {
+       term=$1
+       git check-ref-format refs/bisect/"$term" ||
+       die "$(eval_gettext "'\$term' is not a valid term")"
+       case "$term" in
+       help|start|terms|skip|next|reset|visualize|replay|log|run)
+               die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
+               ;;
+       bad|new)
+               if test "$2" != bad
+               then
+                       # In theory, nothing prevents swapping
+                       # completely good and bad, but this situation
+                       # could be confusing and hasn't been tested
+                       # enough. Forbid it for now.
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       good|old)
+               if test "$2" != good
+               then
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       esac
+}
+
+check_and_set_terms () {
+       cmd="$1"
+       case "$cmd" in
+       skip|start|terms) ;;
+       *)
+               if test -s "$GIT_DIR/BISECT_TERMS" && test "$cmd" != "$TERM_BAD" && test "$cmd" != "$TERM_GOOD"
+               then
+                       die "$(eval_gettext "Invalid command: you're currently in a \$TERM_BAD/\$TERM_GOOD bisect.")"
+               fi
+               case "$cmd" in
+               bad|good)
+                       if ! test -s "$GIT_DIR/BISECT_TERMS"
+                       then
+                               write_terms bad good
+                       fi
+                       ;;
+               new|old)
+                       if ! test -s "$GIT_DIR/BISECT_TERMS"
+                       then
+                               write_terms new old
+                       fi
+                       ;;
+               esac ;;
+       esac
+}
+
+bisect_voc () {
+       case "$1" in
+       bad) echo "bad|new" ;;
+       good) echo "good|old" ;;
+       esac
+}
+
+bisect_terms () {
+       get_terms
+       if ! test -s "$GIT_DIR/BISECT_TERMS"
+       then
+               die "$(gettext "no terms defined")"
+       fi
+       case "$#" in
+       0)
+               gettextln "Your current terms are $TERM_GOOD for the old state
+and $TERM_BAD for the new state."
+               ;;
+       1)
+               arg=$1
+               case "$arg" in
+                       --term-good|--term-old)
+                               printf '%s\n' "$TERM_GOOD"
+                               ;;
+                       --term-bad|--term-new)
+                               printf '%s\n' "$TERM_BAD"
+                               ;;
+                       *)
+                               die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'.
+Supported options are: --term-good|--term-old and --term-bad|--term-new.")"
+                               ;;
+               esac
+               ;;
+       *)
+               usage ;;
+       esac
+}
+
 case "$#" in
 0)
-    usage ;;
+       usage ;;
 *)
-    cmd="$1"
-    shift
-    case "$cmd" in
-    help)
-        git bisect -h ;;
-    start)
-        bisect_start "$@" ;;
-    bad|good)
-        bisect_state "$cmd" "$@" ;;
-    skip)
-        bisect_skip "$@" ;;
-    next)
-        # Not sure we want "next" at the UI level anymore.
-        bisect_next "$@" ;;
-    visualize|view)
-       bisect_visualize "$@" ;;
-    reset)
-        bisect_reset "$@" ;;
-    replay)
-       bisect_replay "$@" ;;
-    log)
-       bisect_log ;;
-    run)
-        bisect_run "$@" ;;
-    *)
-        usage ;;
-    esac
+       cmd="$1"
+       get_terms
+       shift
+       case "$cmd" in
+       help)
+               git bisect -h ;;
+       start)
+               bisect_start "$@" ;;
+       bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
+               bisect_state "$cmd" "$@" ;;
+       skip)
+               bisect_skip "$@" ;;
+       next)
+               # Not sure we want "next" at the UI level anymore.
+               bisect_next "$@" ;;
+       visualize|view)
+               bisect_visualize "$@" ;;
+       reset)
+               bisect_reset "$@" ;;
+       replay)
+               bisect_replay "$@" ;;
+       log)
+               bisect_log ;;
+       run)
+               bisect_run "$@" ;;
+       terms)
+               bisect_terms "$@" ;;
+       *)
+               usage ;;
+       esac
 esac