#!/usr/bin/env bash
#
# Wrapper for git to handle more subdirs at the same time
#

# no params, no action
if [ "$#" -eq "0" ] ; then
    git
    echo
    echo "Additional options available only in this 'g' wrapper:"
    echo
    echo "Usage: g [options] [git commands]"
    echo "   -f         Force - act on all the repos, not only the changed ones"
    echo "   -s         Silent - do not report the repo names."
    echo "   -v         Verbose - Print git commands."
    echo "   -1         report the repos name on the first line of the output as <repo>:"
    echo "   -z         just to some house cleaning (hooks mostly). this is a stand-alone option as in ./g -z"
    echo "   --set-push-user     [username] re-write an existing tree's config with an fd.o commit account name"
    echo "   --last-working      checks out the last known working build (useful for windows)";
    echo "   --set-last-working  adds a note denoting a working build";
    echo "   --push-notes        pushes all notes";
    exit $?
fi

if [ ! "`type -p git`" ]; then
    echo "Cannot find the git binary! Is git installed and is in PATH?"
    exit 1
fi

pushd $(dirname $0) > /dev/null
COREDIR=$(pwd)
popd > /dev/null

refresh_hooks()
{
    repo=$1
    case "$repo" in
        core)
            pushd $COREDIR > /dev/null
            for hook_name in $(ls -1 $COREDIR/git-hooks) ; do
                hook=".git/hooks/$hook_name"
                if [ ! -x "$hook" ] ; then
                    rm -f "$hook"
                    ln -sf "$COREDIR/git-hooks/$hook_name" "$hook"
                fi
            done
            popd > /dev/null
            ;;
        translations)
            if [ -d $COREDIR/clone/translations ] ; then
                pushd $COREDIR/clone/translations > /dev/null
                for hook_name in $(ls -1 $COREDIR/clone/translations/git-hooks); do
                    hook=".git/hooks/$hook_name"
                    if [ ! -x "$hook" ] ; then
                        rm -f "$hook"
                        ln -sf "$COREDIR/clone/translations/git-hooks/$hook_name" "$hook"
                    fi
                done
                # .gitattribute should be per-repo, avoid entangling repos
                if [ -L .gitattributes ] ; then
                    rm -f .gitattributes
                fi
                popd > /dev/null
            fi
            ;;
        binfilter|help|dictionaries)
            if [ -d $COREDIR/clone/$repo ] ; then
                pushd $COREDIR/clone/$repo > /dev/null
                # fixme: we should really keep these per-repo to
                # keep the repos independant. since these two
                # are realy not independant yet, we keep using core's hooks
                for hook_name in $(ls -1 $COREDIR/git-hooks) ; do
                    hook=".git/hooks/$hook_name"
                    if [ ! -x "$hook" ] ; then
                        rm -f "$hook"
                        ln -sf "$COREDIR/git-hooks/$hook_name" "$hook"
                    fi
                done
                # .gitattribute should be per-repo, avoid entangling repos
                if [ -L .gitattributes ] ; then
                    rm -f .gitattributes
                fi
                popd > /dev/null
            fi
            ;;
    esac
}

refresh_all_hooks()
{
    repos="core $(cat "$COREDIR/bin/repo-list")"
    for repo in $repos ; do
        refresh_hooks $repo
    done
}

postprocess()
{
    rc=$1
    if $DO_HOOK_REFRESH ; then
        refresh_all_hooks
    fi

    exit $rc;
}

CLONEDIR="$COREDIR/clone"
if [ ! -e ${CLONEDIR} ]; then mkdir -p "$CLONEDIR"; fi

# extra params for some commands, like log
EXTRA=
COMMAND="$1"
PAGER=
RELATIVIZE=1
PUSH_ALL=
PUSH_USER=
PUSH_NOTES=
LAST_WORKING=
SET_LAST_WORKING=
ALLOW_EMPTY=
KEEP_GOING=0
REPORT_REPOS=1
REPORT_COMMANDS=0
REPORT_COMPACT=0
DO_HOOK_REFRESH=false

while [ "${COMMAND:0:1}" = "-" ] ; do
    case "$COMMAND" in
        -f) KEEP_GOING=1
            ;;
        -s) REPORT_REPOS=0
            ;;
        -v) REPORT_COMMANDS=1
            ;;
        -1) REPORT_COMPACT=1
            ;;
        --set-push-user)
            shift
            PUSH_USER="$1"
            ;;
        --last-working) LAST_WORKING=1
            ;;
        --set-last-working) SET_LAST_WORKING=1
            ;;
        --push-notes) PUSH_NOTES=1
            ;;
        -z)
            DO_HOOK_REFRESH=true
            postprocess 0
            ;;
    esac
    shift
    COMMAND="$1"
done

case "$COMMAND" in
    apply)
        EXTRA="-p0 --stat --apply --index --ignore-space-change --whitespace=error"
        RELATIVIZE=0
        ;;
    clone|fetch|pull)
        DO_HOOK_REFRESH=true
        ;;
    diff)
        PAGER='--no-pager'
        REPORT_REPOS=0
        ;;
    log)
        if [ "$#" = "1" ] ; then
            EXTRA='-1'
        fi
        PAGER='--no-pager'
        ;;
    push)
        if [ "$#" != "1" ] ; then
            PUSH_ALL=1
        fi
        ;;
esac

# absolutize the parameters first
unset FILES
FILESNUM=0
while shift ; do
    PARAM="$1"
    if [ -z "$PARAM" ] ; then
        continue
    elif [ "${PARAM:0:1}" = "-" ] ; then
        if [ \( "$COMMAND" = "checkout" -a "$PARAM" = "-b" \) -o \
             \( "$COMMAND" = "clone"    -a "$PARAM" = "--reference" \) -o \
             \( "$COMMAND" = "commit"   -a "$PARAM" = "-m" \) -o \
             \( "$COMMAND" = "commit"   -a "$PARAM" = "-am" \) -o \
             \( "$COMMAND" = "tag"      -a "$PARAM" = "-m" \) ]
        then
            # params that take an argument
            FILES[$FILESNUM]="$PARAM"
            FILESNUM=$(($FILESNUM+1))
            shift
            FILES[$FILESNUM]="$1"
            FILESNUM=$(($FILESNUM+1))
        else
            if [ "$COMMAND" = "commit" -a "$PARAM" = "-F" ]
            then
                shift
                # this still needs some magic to handle relative paths
                EXTRA="${EXTRA} -F ${1}"
            else
                [ "$COMMAND" = "commit" -a "$PARAM" = "--allow-empty" ] && ALLOW_EMPTY=1
                FILES[$FILESNUM]="$PARAM"
                FILESNUM=$(($FILESNUM+1))
            fi
        fi
    else
        if [ "$COMMAND" = "apply" ] ; then
            grep -qs $'^+ *\t' "$PARAM" && {
                echo "Patch '$PARAM' introduces tabs in indentation, aborting."
                echo
                echo "Please fix the patch (something like s/^\(+ *\)\t/\1    /) and try again."
                echo
                exit 1
            }
        fi
        if [ "$COMMAND" == "rev-parse" ] ; then
            # this is not a file
            FILES[$FILESNUM]="$PARAM"
            FILESNUM=$(($FILESNUM+1))
        else
            # make the paths absolute
            FILES[$FILESNUM]=$(perl -e 'use Cwd "abs_path"; print abs_path(shift);' "$PARAM")
            if [ -z "${FILES[$FILESNUM]}" -o ! -e "${FILES[$FILESNUM]}" ] ; then
                # it is probably not a file, but a tag name, or something
                FILES[$FILESNUM]="$PARAM"
            fi
            FILESNUM=$(($FILESNUM+1))
       fi
    fi
done

# do it!
DIRS="core $(cd $CLONEDIR ; ls)"
if [ "$COMMAND" = "clone" ] ; then
    DIRS=$(cat "$COREDIR/bin/repo-list")
fi
for REPO in $DIRS ; do
    DIR="$CLONEDIR/$REPO"
    NAME="$REPO"
    if [ "$REPO" = "core" ] ; then
        DIR="$COREDIR"
        NAME="main repo"
    fi

    if [ -d "$DIR" -a "z$PUSH_USER" != "z" ]; then
       echo "setting up push url for $DIR"
       (cd $DIR && git config remote.origin.pushurl "ssh://${PUSH_USER}@git.freedesktop.org/git/libreoffice/${REPO}")
    elif [ -d "$DIR" -a "z$LAST_WORKING" != "z" ]; then
       echo "fetching notes for $REPO ..."
       (cd $DIR && git fetch origin 'refs/notes/*:refs/notes/*')
       hash=`(cd $DIR && git log --pretty='%H %N' | grep 'win32 working build' | head -n1 | sed 's/ win32.*//')`
       if test "z$hash" != "z"; then
       echo "update to $hash"
       (cd $DIR && git checkout $hash)
       else
       echo "Warning: missing known working note on repo $REPO"
       fi
    elif [ -d "$DIR" -a "z$SET_LAST_WORKING" != "z" ]; then
       echo "fetching notes for $REPO ..."
       (cd $DIR && git fetch origin 'refs/notes/*:refs/notes/*')
       (cd $DIR && git notes add -m 'win32 working build')
    elif [ -d "$DIR" -a "z$PUSH_NOTES" != "z" ]; then
       echo "pushing notes for $REPO ..."
       (cd $DIR && git push origin 'refs/notes/*:refs/notes/*')
    elif [ \( -d "$DIR" -a -d "$DIR"/.git \) -o \( "$COMMAND" = "clone" \) ] ; then
        (
            # executed in a subshell
            if [ "$COMMAND" != "clone" ] ; then
                cd "$DIR"
            else
                cd "$CLONEDIR"
            fi

            # relativize the absolutized params again if we want to operate
            # only on the files belonging to this exact repo
            if [ "$RELATIVIZE" = "1" -a -n "$FILES" ] ; then
                FILESNUM=0
                INSERTNUM=0
                PWD=$(pwd)
                PWDLEN=$(pwd | wc -c)
                for I in "${FILES[@]}" ; do
                    I="${I//@REPO@/${REPO}}"
                    unset FILES[$FILESNUM]
                    FILESNUM=$(($FILESNUM+1))
                    # filter out files that don't belong to this repo
                    if [ \( "${I:0:1}" = "/" \) -a \( "$COMMAND" != "clone" \) ] ; then
                        if [ "${I:0:$PWDLEN}" = "$PWD/" ] ; then
                            FILES[$INSERTNUM]="${I:$PWDLEN}"
                            INSERTNUM=$(($INSERTNUM+1))
                        fi
                    else
                        FILES[$INSERTNUM]="$I"
                        INSERTNUM=$(($INSERTNUM+1))
                    fi
                done
                [ "$INSERTNUM" = "0" ] && exit 0
            fi

            # some extra params
            case "$COMMAND" in
                apply)
                    for I in * ; do
                        if [ -d "$I" ] ; then
                            EXTRA="$EXTRA --include=$I/*"
                        else
                            EXTRA="$EXTRA --include=$I"
                        fi
                    done
                    ;;
                commit)
                    if [ "$ALLOW_EMPTY" != "1" ] ; then
                        [ -z "$(git diff-index --name-only HEAD --)" ] && exit 0
                    fi
                    ;;
                push)
                    if [ "$PUSH_ALL" != "1" ] ; then
                        [ -n "$(git rev-list @{upstream}..HEAD)" ] || exit 0
                    fi
                    ;;
                status)
                    LOCALCOMMITS="$(git rev-list @{upstream}..HEAD)"
                    if [ -z "$LOCALCOMMITS" ] ; then
                        [ -z "$(git diff-index --name-only HEAD --)" ] && exit 0
                    fi
                    ;;
                clone)
                    EXTRA="$(git config remote.origin.url)"
            EXTRA=${EXTRA/core/${REPO}}
                    ;;
            esac

            # do it!
            if [ "$COMMAND" != "clone" -o ! -d $DIR ] ; then
                if [ "$REPORT_REPOS" = "1" -a "$COMMAND" != "grep" ] ; then
                    if [ "$REPORT_COMPACT" = "1" ] ; then
                        echo -n "${REPO}:"
                    else
                        echo "===== $NAME ====="
                    fi
                fi
                if [ "$REPORT_COMMANDS" = "1" ] ; then
                    echo "+ git $PAGER $COMMAND $EXTRA ${FILES[@]}"
                fi
                git $PAGER "$COMMAND" $EXTRA "${FILES[@]}"
                RETURN=$?
            fi

            # now we can change the dir in case of clone as well
            if [ "$COMMAND" = "clone" ] ; then
                cd $DIR
            fi

            case "$COMMAND" in
                pull|clone)
                    # update links
                    if [ "$DIR" != "$COREDIR" ]; then
                        for link in $(ls) ; do
                            if [ ! -e "$COREDIR/$link" ] ; then
                                if test -h "$COREDIR/$link"; then
                                    rm "$COREDIR/$link"
                                    echo -n "re-"
                                fi
                                echo "creating missing link $link"
                                ln -s "$DIR/$link" "$COREDIR/$link"
                            fi
                        done
                    fi
                    ;;
                status)
                    # git status returns error in some versions, clear that
                    RETURN=0
                    ;;
                grep)
                    # git grep return an 'error' if nothing is found
                    # still we should continue grepping the other repos
                    RETURN=0
                    ;;
            esac
            if [  "$KEEP_GOING" = "1" ] ; then
                RETURN=0
            fi

            exit $RETURN
        ) || postprocess $?
    fi
done

# Cleanup the broken links
if [ "$COMMAND" = "pull" ] ; then
    for link in $(ls $COREDIR) ; do
        if [ -h "$COREDIR/$link" -a ! -e "$COREDIR/$link" ]; then
            echo "Removing broken link $link"
            rm $COREDIR/$link
        fi
    done
fi

# warn
if [ "$COMMAND" = "apply" ] ; then
    echo
    echo "Don't forget to check the status & commit now ;-)"
    echo
fi

postprocess $?

# vi:set shiftwidth=4 expandtab: