#!/usr/bin/env bash

#
# --------------------------------------------------------
# Usage
# --------------------------------------------------------
#
# git top [branch-name]
#
# --------------------------------------------------------
# Summary
# --------------------------------------------------------
#
# Performs a pull from origin onto the default branch before rebasing the
# current branch on to the default branch.
#
# Author: Tony Grosinger
#
# --------------------------------------------------------
# Installation
# --------------------------------------------------------
#
# Place the following in your .bashrc then place this file on your path.
#
#    _git_top() {
#        if [[ "${COMP_LINE}" =~ ^git\ top.*$ ]]; then
#            __gitcomp_nl "$(__git_heads)"
#        else
#            __git_main
#        fi
#    }
#
# Ensure the following is in your .gitconfig alias section:
#
#    default-branch = "!git symbolic-ref refs/remotes/origin/HEAD | cut -f4 -d/"
#

VERSION="v0.0.2"

GREEN="\e[32m"
BLUE="\e[34m"
RED="\e[91m"
CLEAR="\e[0m"

DEFAULT_BRANCH=$(git default-branch)

version() {
    echo "git top ${VERSION}"
    echo ""
}

usage() {
    echo "usage: git top [branch-name]"
    echo ""
    echo "If branch name is omitted, current branch will be updated"
}

print_cmd() {
    echo -e "${GREEN}>> ${CLEAR}${@}"
}

print_status() {
    echo -e "${BLUE}${@}${CLEAR}"
}

print_fatal() {
    echo -e >&2 "${RED}Fatal: ${CLEAR}${@}"
}

exec_cmd() {
    print_cmd ${1}
    ${1}

}

require_git_repo() {
    git rev-parse --is-inside-work-tree > /dev/null 2>&1

    if [ $? != 0 ]; then
        print_fatal "Not a git repository"
        exit 1
    fi
}

require_clean_work_tree() {
    # Outputs an error to stderr if working tree is not clean, then exits
    # Function retrieved from http://www.spinics.net/lists/git/msg142043.html
    # http://stackoverflow.com/questions/3878624/how-do-i-programmatically-determine-if-there-are-uncommited-changes

    # Update the index
    git update-index -q --ignore-submodules --refresh
    err=0

    # Disallow unstaged changes in the working tree
    if ! git diff-files --quiet --ignore-submodules --
    then
        echo >&2 "cannot $1: you have unstaged changes."
        git diff-files --name-status -r --ignore-submodules -- >&2
        err=1
    fi

    # Disallow uncommitted changes in the index
    if ! git diff-index --cached --quiet HEAD --ignore-submodules --
    then
        echo >&2 "cannot $1: your index contains uncommitted changes."
        git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
        err=1
    fi

    if [ $err = 1 ]
    then
        echo >&2 "Please commit or stash them."
        exit 1
    fi
}

checkout() {
    exec_cmd "git checkout -q ${1}"
}

pull_remote() {
    checkout "${DEFAULT_BRANCH}"
    exec_cmd "git pull -r -q"

    if [ $? != 0 ]; then
        echo >&2 "Could not pull on ${DEFAULT_BRANCH}, please check that upstream is set and try again."
        exit 1
    fi
}

rebase() {
    local startbranch=${1}
    exec_cmd "git rebase -q ${DEFAULT_BRANCH} ${startbranch}"

    if [ $? != 0 ]; then
        echo >&2 "Could not rebase ${startbranch} on ${DEFAULT_BRANCH}, rolling back changes."
        echo >&2 "Please manually resolve conflicts."

        git rebase --abort
        exit 1
    fi
}

main() {
    require_git_repo
    local startbranch=`git rev-parse --abbrev-ref HEAD`

    if [ $# = 1 ]; then
        case "$1" in
            "-h"|"--help")
                usage
                exit 0
                ;;
            "-v"|"--version")
                version
                exit 0
                ;;
            *)
                startbranch=$1
                ;;
        esac
    elif [ $# != 0 ]; then
        print_fatal "Invalid number of arguments provided"
        echo ""
        usage
        exit 1
    fi

    git show-ref --verify -q refs/heads/${startbranch}
    if [ $? != 0 ]; then
        print_fatal "Branch \"${startbranch}\" does not exist"
        exit 1
    fi

    require_clean_work_tree

    git fetch -p

    if [ ${startbranch} = "${DEFAULT_BRANCH}" ]; then
        print_status "Already on ${DEFAULT_BRANCH}, only performing git pull"
        pull_remote
    else
        pull_remote
        rebase $startbranch
    fi

    checkout $startbranch

    print_status "Branch update complete."
}

main "$@"

