#!/usr/bin/env bash

# Run a Git command in all repositories under a path

# A better class of script...
set -o errexit          # Exit on most errors (see the manual)
set -o errtrace         # Make sure any error trap is inherited
set -o nounset          # Disallow expansion of unset variables
set -o pipefail         # Use last non-zero exit code in a pipeline
#set -o xtrace          # Trace the execution of the script (debug)

# DESC: Usage help
# ARGS: None
# OUTS: None
function script_usage() {
    cat << EOF
Usage: [OPTIONS] [COMMAND]
     -h|--help                  Displays this help
     -v|--verbose               Displays verbose output
    -nc|--no-colour             Disables colour output

     -p|--path                  Directory path(s)
     -r|--recurse               Recurse into subdirectories
EOF
}

# DESC: Parameter parser
# ARGS: $@ (optional): Arguments provided to the script
# OUTS: Variables indicating command-line parameters and options
function parse_params() {
    local param
    declare -g -a cmd_paths

    while [[ $# -gt 0 ]]; do
        param="$1"
        shift
        case $param in
            -h | --help)
                script_usage
                exit 0
                ;;
            -v | --verbose)
                verbose=true
                ;;
            -nc | --no-colour)
                no_colour=true
                ;;
            -p | --path)
                cmd_paths+=("$1")
                shift
                ;;
            -r | --recurse)
                recurse=true
                ;;
            *)
                if [[ -n $* ]]; then
                    git_cmd="$param $*"
                else
                    git_cmd="$param"
                fi

                break
                ;;
        esac
    done

    if [[ -z ${git_cmd-} ]]; then
        script_exit 'No git command provided.'
    fi

    if [[ -n ${cmd_paths[*]-} ]]; then
        for dir in "${cmd_paths[@]}"; do
            if [[ ! -d $dir ]]; then
                script_exit "Path is not a directory: $dir"
            fi
        done
    else
        cmd_paths+=(.)
    fi
}

# DESC: Find some important paths we need before script_init() is called
# ARGS: None
# OUTS: $script_real_path: The full canonicalized path to the script
#       $script_real_dir: The canonicalized directory path of the script
#       $script_real_name: The canonicalized file name of the script
#       $dotfiles_path: The dotfiles directory path
#       $dotfiles_name: The dotfiles directory name
# shellcheck disable=SC2034
function early_init() {
    # Determine the physical directory this script resides in. This approach is
    # necessarily clumsy as simple methods like "readlink -f" and "realpath"
    # are not platform independent. We have to work on Linux, BSD, OS X, etc...
    script_real_path="$(perl -MCwd -le 'print Cwd::abs_path(shift)' "${BASH_SOURCE[0]}")"
    script_real_dir="$(dirname "$script_real_path")"
    script_real_name="$(basename "$script_real_path")"
    readonly script_real_path script_real_dir script_real_name

    # Dotfiles directory path & name
    dotfiles_path="$(cd -P "$script_real_dir/../../" && pwd)"
    dotfiles_name="$(basename "$dotfiles_path")"
    readonly dotfiles_path dotfiles_name
}

# DESC: Check the directory belongs to a Git repository
# ARGS: $1 (required): Path to the directory to check
# OUTS: $is_git_repo: Set to "true" if the provided directory belongs to a Git
#                     repository, otherwise "false".
function check_is_git_repo() {
    if [[ $# -lt 1 ]]; then
        script_exit 'Missing required argument to check_is_git_repo()!' 2
    fi

    is_git_repo='false'

    local git_dir="$1"
    if [[ -d $git_dir ]]; then
        local orig_dir="$PWD"

        cd "$git_dir"
        if git rev-parse --git-dir > /dev/null 2>&1; then
            is_git_repo='true'
        fi

        cd "$orig_dir"
    fi
}

# DESC: Run Git command if directory is a repository or recurse if specified
# ARGS: $1 (required): Directory path to run in
# OUTS: None
function git_repo_invoke() {
    local base_dir="$1"

    (
        cd -- "$base_dir"
        if git rev-parse --git-dir > /dev/null 2>&1; then
            local repo_name
            repo_name="$(basename "$PWD")"

            pretty_print "*** $repo_name ***"

            # shellcheck disable=SC2086
            git $git_cmd

            echo
        elif [[ -n ${recurse-} ]]; then
            for dir in */; do
                git_repo_invoke "$dir"
            done
        fi
    )
}

# DESC: Main control flow
# ARGS: $@ (optional): Arguments provided to the script
# OUTS: None
function main() {
    early_init

    # shellcheck source=scripts/bin/bash-template.sh
    source "$dotfiles_path/scripts/bin/bash-template.sh"

    trap script_trap_err ERR
    trap script_trap_exit EXIT

    script_init "$@"
    parse_params "$@"
    colour_init

    check_binary git fatal

    shopt -s nullglob
    for cmd_path in "${cmd_paths[@]}"; do
        check_is_git_repo "$cmd_path"
        if [[ $is_git_repo == 'true' ]]; then
            git_repo_invoke "$cmd_path"
        else
            for dir in "$cmd_path"/*/; do
                git_repo_invoke "$dir"
            done
        fi
    done
}

# Make it rain
main "$@"

# vim: syntax=sh cc=80 tw=79 ts=4 sw=4 sts=4 et sr
