#!/bin/sh -e
#
# Copyright (C) 2008,2009 by Robert Millan
# Copyright (C) 2009 by Peter Samuelson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

dir=.svn-bisect
: ${SVN:=svn}

svn_info ()
{
    LC_ALL=C $SVN info | awk "/^$1:/{ print \$2 }"
}

usage ()
{
  cat << EOF
Usage:
  $0 start [good_rev [bad_rev]]
  $0 good [revision]
  $0 bad [revision]
  $0 run {command}
  $0 status
  $0 reset
EOF
}

do_init ()
{
    rm -rf $dir
    mkdir -p $dir
    svn_info URL > $dir/url
    svn_info Revision > $dir/start
    if [ -n "$1" ]; then do_good_bad good "$1"; fi
    if [ -n "$2" ]; then do_good_bad bad "$2"; fi
}

do_reset ()
{
    if [ ! -d $dir ]; then return 0; fi
    if [ ! -f $dir/url ]; then
        echo >&2 "Error: no $dir/url file"
        exit 1
    fi
    url=$(cat $dir/url)
    rev=$(cat $dir/start)
    $SVN switch -r$rev "$url@$rev"
    rm -fr $dir
    echo "Now at r$rev in $url"
}

do_status ()
{
    if [ ! -d $dir ]; then
        status='not initialized'
    elif [ -f $dir/found ]; then
        status="found bad revision r$(cat $dir/found)"
    elif [ ! -f $dir/revs ]; then
        if [ ! -f $dir/good ]; then
            status='still need a "good" revision'
        elif [ ! -f $dir/bad ]; then
            status='still need a "bad" revision'
        else
            status='???'
        fi
    else
        status="r$(head -n1 $dir/revs) is good, r$(tail -n1 $dir/revs) is bad"
        status="$status, $(($(wc -l < $dir/revs) - 2)) unknown revs in between"
    fi
    echo "svn-bisect: status: $status"
}

do_good_bad ()
{
    what=$1
    shift
    good=0; bad=99999999;
    if [ -n "$1" ] ; then
        current=$(echo "$1" | sed s/^r*// | tee $dir/$what)
    else
        current=$(svn_info Revision | tee $dir/$what)
    fi

    if [ -f $dir/revs ]; then
        cat $dir/revs | while read rev; do
            if { [ $what = good ] && [ $rev -ge $current ]; } ||
               { [ $what = bad ] && [ $rev -le $current ]; }; then
                echo $rev
            fi
        done > $dir/revs.new;
        mv $dir/revs.new $dir/revs
    elif [ -f $dir/good ] && [ -f $dir/bad ]; then
        $SVN log -q -r$(cat $dir/good):$(cat $dir/bad) |
          awk '/^r/{print $1}' | cut -c2- > $dir/revs
    else
        return 0
    fi
    good=$(head -n1 $dir/revs)
    bad=$(tail -n1 $dir/revs)
    url=$(cat $dir/url)
    start=$(cat $dir/start)
    n=$(wc -l < $dir/revs)
    case $n in
        0)
            echo >&2 "Error: no good or bad revs"
            exit 1 ;;
        1)
            echo >&2 "Error: r$(cat $dir/revs) is marked as both good and bad"
            exit 1 ;;
        2)
            echo "Regression found!"
            echo "Last good revision: r$good"
            echo "First bad revision:"
            $SVN log -r$bad
            echo "Use '$0 reset' or 'rm -r $dir' to clean up"
            echo $bad > $dir/found
            return 0 ;;
    esac
    target=$(head -n $(((n+1)/2)) $dir/revs | tail -n1)
    echo "Switching to r$target ..."
    $SVN switch -r$target "$url@$start"
    url2=$(svn_info URL)
    if [ "$url" != "$url2" ]; then
        echo "r$target is in $url2"
    fi
    return $?
}

do_run ()
{
    cmd=$1
    shift
    while [ -d $dir ] && [ ! -f $dir/found ]; do
        set +e
        eval ${cmd}
        status=$?
        set -e
        case $status in
            125) $0 skip ;;
            0) $0 good ;;
            *) $0 bad ;;
        esac
    done
}


cmd=$1
shift
case "$cmd" in

    start) do_init "$@" ;;

    bad) do_good_bad bad "$@" ;;

    good) do_good_bad good "$@" ;;

    run) do_run "$@" ;;

    status) do_status ;;

    reset) do_reset ;;

    -h|--help)
        usage
        exit 0 ;;

    "")
        usage >&2
        exit 1 ;;

    *)
        echo "Unknown parameter \`$cmd'" >&2
        usage >&2
        exit 1 ;;
esac
