Big tool rename.
[git.git] / git-bisect.sh
diff --git a/git-bisect.sh b/git-bisect.sh
new file mode 100755 (executable)
index 0000000..605d58a
--- /dev/null
@@ -0,0 +1,178 @@
+#!/bin/sh
+. git-sh-setup || dir "Not a git archive"
+
+usage() {
+    echo >&2 'usage: git bisect [start|bad|good|next|reset|visualize]
+git bisect start               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.
+git bisect next                        find next bisection to test and check it out.
+git bisect reset [<branch>]    finish bisection search and go back to branch.
+git bisect visualize            show bisect status in gitk.'
+    exit 1
+}
+
+bisect_autostart() {
+       test -d "$GIT_DIR/refs/bisect" || {
+               echo >&2 'You need to start by "git bisect start"'
+               if test -t 0
+               then
+                       echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+                       read yesno
+                       case "$yesno" in
+                       [Nn]*)
+                               exit ;;
+                       esac
+                       bisect_start
+               else
+                       exit 1
+               fi
+       }
+}
+
+bisect_start() {
+        case "$#" in 0) ;; *) usage ;; esac
+       #
+       # Verify HEAD. If we were bisecting before this, reset to the
+       # top-of-line master first!
+       #
+       head=$(readlink $GIT_DIR/HEAD) || die "Bad HEAD - I need a symlink"
+       case "$head" in
+       refs/heads/bisect*)
+               git checkout master || exit
+               ;;
+       refs/heads/*)
+               ;;
+       *)
+               die "Bad HEAD - strange symlink"
+               ;;
+       esac
+
+       #
+       # Get rid of any old bisect state
+       #
+       rm -f "$GIT_DIR/refs/heads/bisect"
+       rm -rf "$GIT_DIR/refs/bisect/"
+       mkdir "$GIT_DIR/refs/bisect"
+}
+
+bisect_bad() {
+       bisect_autostart
+       case "$#" in
+       0)
+               rev=$(git-rev-parse --verify HEAD) ;;
+       1)
+               rev=$(git-rev-parse --verify "$1") ;;
+       *)
+               usage ;;
+       esac || exit
+       echo "$rev" > "$GIT_DIR/refs/bisect/bad"
+       bisect_auto_next
+}
+
+bisect_good() {
+       bisect_autostart
+        case "$#" in
+       0)    revs=$(git-rev-parse --verify HEAD) || exit ;;
+       *)    revs=$(git-rev-parse --revs-only --no-flags "$@") &&
+               test '' != "$revs" || die "Bad rev input: $@" ;;
+       esac
+       for rev in $revs
+       do
+               rev=$(git-rev-parse --verify "$rev") || exit
+               echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
+       done
+       bisect_auto_next
+}
+
+bisect_next_check() {
+       next_ok=no
+        test -f "$GIT_DIR/refs/bisect/bad" &&
+       case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in
+       refs/bisect/good-\*) ;;
+       *) next_ok=yes ;;
+       esac
+       case "$next_ok,$1" in
+       no,) false ;;
+       no,fail)
+           echo >&2 'You need to give me at least one good and one bad revisions.'
+           exit 1 ;;
+       *)
+           true ;;
+       esac
+}
+
+bisect_auto_next() {
+       bisect_next_check && bisect_next
+}
+
+bisect_next() {
+        case "$#" in 0) ;; *) usage ;; esac
+       bisect_autostart
+       bisect_next_check fail
+       bad=$(git-rev-parse --verify refs/bisect/bad) &&
+       good=$(git-rev-parse --sq --revs-only --not \
+               $(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
+       rev=$(eval "git-rev-list --bisect $good $bad") || exit
+       if [ -z "$rev" ]; then
+           echo "$bad was both good and bad"
+           exit 1
+       fi
+       if [ "$rev" = "$bad" ]; then
+           echo "$rev is first bad commit"
+           git-diff-tree --pretty $rev
+           exit 0
+       fi
+       nr=$(eval "git-rev-list $rev $good" | wc -l) || exit
+       echo "Bisecting: $nr revisions left to test after this"
+       echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
+       git checkout new-bisect || exit
+       mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
+       ln -sf refs/heads/bisect "$GIT_DIR/HEAD"
+}
+
+bisect_visualize() {
+       bisect_next_check fail
+       gitk bisect/bad --not `cd "$GIT_DIR/refs" && echo bisect/good-*`
+}
+
+bisect_reset() {
+       case "$#" in
+       0) branch=master ;;
+       1) test -f "$GIT_DIR/refs/heads/$1" || {
+              echo >&2 "$1 does not seem to be a valid branch"
+              exit 1
+          }
+          branch="$1" ;;
+        *)
+           usage ;;
+       esac
+       git checkout "$branch" &&
+       rm -fr "$GIT_DIR/refs/bisect"
+       rm -f "$GIT_DIR/refs/reads/bisect"
+}
+
+case "$#" in
+0)
+    usage ;;
+*)
+    cmd="$1"
+    shift
+    case "$cmd" in
+    start)
+        bisect_start "$@" ;;
+    bad)
+        bisect_bad "$@" ;;
+    good)
+        bisect_good "$@" ;;
+    next)
+        # Not sure we want "next" at the UI level anymore.
+        bisect_next "$@" ;;
+    visualize)
+       bisect_visualize "$@" ;;
+    reset)
+        bisect_reset "$@" ;;
+    *)
+        usage ;;
+    esac
+esac