#!/usr/bin/env bash

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

default_interval_in_seconds=10
default_hooks_file="$script_dir""/git-repo-watcher-hooks"

print_usage() {
    echo ""
    echo "NAME"
    echo "    git-repo-watcher -- keeps a git repository in sync with its origin"
    echo "SYNOPSIS"
    echo "    git-repo-watcher -d <directory> [-h <hooks-file>] [-i <interval>] [-o]"
    echo "DESCRIPTION"
    echo "    The following options are available:"
    echo "    -d    The path to the git repository"
    echo "    -i    Watch interval time in seconds (defaults to 10 seconds)"
    echo "    -o    Run once"
    echo "    -h    Custom hooks file"
    echo ""
    exit 1
}

while getopts ":d:i:h:o" options; do
    case "${options}" in
    d)
        git_repository_dir=${OPTARG}
        ;;
    h)
        hooks_file=${OPTARG}
        ;;
    i)
        interval_in_seconds=${OPTARG}
        ;;
    o)
        run_once=true
        ;;
    *)
        print_usage
        ;;
    esac
done
shift $((OPTIND - 1))

# Validating given git directory
if [[ -z "$git_repository_dir" ]]; then
    echo -e "\nERROR: Git directory (-d) not given!" >&2
    print_usage
fi

if [[ ! -d "$git_repository_dir/.git" ]] || [[ ! -r "$git_repository_dir/.git" ]]; then
    echo "ERROR: Git directory (-d) not found: '$git_repository_dir/.git'!" >&2
    print_usage
fi

if [[ ! -w "$git_repository_dir/.git" ]]; then
    echo "ERROR: Missing write permissions for the git directory (-d): '$git_repository_dir/.git'!" >&2
    print_usage
fi

# Convert to absolute file path if necessary
if [[ "$git_repository_dir" != /* ]]; then
    git_repository_dir="$PWD/$git_repository_dir"
fi

# Validating given hook file
[[ -z "${hooks_file}" ]] && hooks_file="$default_hooks_file"

if [[ -f "${hooks_file}" ]] && [[ -r "${hooks_file}" ]]; then
    # shellcheck source=git-repo-watcher-hooks
    source "${hooks_file}"
else
    echo "ERROR: Hooks (-h) file not found: '$hooks_file'" >&2
    print_usage
fi

# Validating given interval
if [[ -z "$interval_in_seconds" ]]; then
    interval_in_seconds="$default_interval_in_seconds"
fi

# Executes user hooks
#
# $1    - Hook name
# $2-$4 - Hook arguments
hook() {
    hook_name="$1"
    shift
    if [[ "$(type -t "$hook_name")" == "function" ]]; then
        eval "$hook_name $*"
    fi
}

# Pulls commit from remote git repository
#
# $1 - Git repository name
# $2 - Branch name
pull_change() {
    git pull
    exit_code=$?

    commit_message=$(git log -1 --pretty=format:"%h | %an | %ad | %s")

    if [[ $exit_code -eq 1 ]]; then
        hook "pull_failed" "$1" "$2" "$commit_message"
    else
        hook "change_pulled" "$1" "$2" "$(printf '%q\n' "$commit_message")"
    fi
}

while true; do

    cd "$git_repository_dir" || exit 1

    if [[ -f ".git/index.lock" ]]; then
        echo "ERROR: Git repository is locked, waiting to unlock" >&2

        if [[ $run_once ]]; then
            break
        fi

        sleep $interval_in_seconds
        continue
    fi

    git fetch

    repo_name=$(basename -s .git "$(git config --get remote.origin.url)")

    previous_branch="$branch"
    branch=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p')

    if [[ -z $branch ]]; then
        echo "ERROR: Unable to get branch" >&2
        exit 1
    fi

    if [[ -n $previous_branch ]] && [[ "$previous_branch" != "$branch" ]]; then
        hook "branch_changed" "$repo_name" "$branch" "$previous_branch"
    fi

    upstream="$(git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>/dev/null)"

    # upstream was not configured
    if [[ -z "$upstream" ]]; then
        hook "upstream_not_set" "$repo_name" "$branch"

        if [[ $run_once ]]; then
            break
        fi

        sleep $interval_in_seconds
        continue
    fi

    git_local=$(git rev-parse @)
    git_remote=$(git rev-parse "$upstream")
    git_base=$(git merge-base @ "$upstream")

    if [[ -z $started ]]; then
        started=true
        hook "startup" "$repo_name" "$branch"
    fi

    if [[ "$git_local" == "$git_remote" ]]; then
        hook "no_changes" "$repo_name" "$branch"
    elif [[ "$git_local" == "$git_base" ]]; then
        hook "pull_change" "$repo_name" "$branch"
    elif [[ "$git_remote" == "$git_base" ]]; then
        hook "local_change" "$repo_name" "$branch"
    else
        hook "diverged" "$repo_name" "$branch"
    fi

    if [[ $run_once ]]; then
        break
    fi

    sleep $interval_in_seconds

done
