#!/bin/bash
# -*- coding: utf-8; mode: sh; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=sh:et:sw=4:ts=4:sts=4

# Note:
# This script is sourced by the mpbb wrapper script.
# Do not execute this directly!

checkout-usage() {
    # "prog" is defined in mpbb-help.
    # shellcheck disable=SC2154
    cat <<EOF
usage: $prog [<global opts>] checkout [<opts>]

Obtain a working copy of the jobs tools and ports tree and configure
MacPorts to use the latter as a port source.

Options:

  --git[=<path>]
    Use Git to obtain the jobs tools and ports tree; this is the default
    behavior. The path to a Git client may be provided explicitly,
    otherwise \`git' is used. Cannot be specified together with --svn.

  --jobs-url=<URL>
    URL to a repository containing the jobs tools. Only used when
    checking out a new working copy. Defaults to
    \`https://github.com/macports/macports-infrastructure.git' for Git
    and
    \`https://github.com/macports/macports-infrastructure.git/trunk/jobs'
    for Subversion.

  --ports-branch=<branch>
    The branch of the remote repository from which the ports tree will
    be checked out. Only used with Git; defaults to \`master'.

  --ports-commit=<commit>
    A commit or revision at which the ports tree will be checked out.
    Any specifier understood by the version-control client may be used.
    Defaults to \`FETCH_HEAD' for Git and \`HEAD' for Subversion.

  --ports-url=<URL>
    URL to a repository containing the ports tree. Only used when
    checking out a new working copy. Defaults to
    \`https://github.com/macports/macports-ports.git' for Git and
    \`https://github.com/macports/macports-ports.git/trunk' for
    Subversion.

  --svn[=<path>]
    Use Subversion to obtain the jobs tools and ports tree. The path to
    a Subversion client may be provided explicitly, otherwise \`svn' is
    used. Cannot be specified together with --git.

Run \`$prog help' for global options and a list of other subcommands.
EOF
}

is-empty() {
    if [[ $# -ne 1 || -z $1 ]]; then
        err 'is-empty requires a single non-null argument'
        return 2
    fi
    (shopt -s dotglob nullglob; f=("$1"/*); (( ! ${#f[@]} )))
}

git-checkout() {
    if (( $# < 2 )); then
        err 'git-checkout requires at least two arguments'
        return 2
    fi

    local -r dst=$1 src=$2 commitish=${3-FETCH_HEAD} branch=${4-master}

    # top is null unless dst exists somewhere in a Git working tree.
    local -r top=$({ cd "$dst" && "$git" rev-parse --show-toplevel; } 2>/dev/null)
    if ! { [[ -z $top ]] && is-empty "$dst" || [[ $top -ef $dst ]]; }; then
        err "\`$dst' is not an empty directory or" \
            'the top level of a Git working tree'
        return 1
    fi

    printf "\n---> Updating Git repository from \`%s' branch of \`%s'\n" \
        "$branch" "$src"
    (
        # "git init" creates the intermediate directories and is safe to
        # run in an existing repository.
        "$git" init "$dst" || exit

        # Change directories explicitly because Lion's Git (1.7.12.4) is
        # too old to understand "git -C" (requires 1.8.5).
        cd "$dst" || exit

        # Fetch directly from the URL to avoid fussing with remotes and
        # to allow switching sources easily.
        "$git" fetch --tags "$src" "$branch" || exit

        # Maintain master to prevent Git from garbage-collecting the
        # fetched objects.
        "$git" update-ref -m "mpbb checkout $(date -u +%Y-%m-%dT%TZ)" \
                    refs/heads/master FETCH_HEAD || exit

        # Only update the working tree here, at the very end.
        "$git" checkout --detach "$commitish"
    )
}

svn-checkout() {
    if (( $# < 2 )); then
        err 'svn-checkout requires at least two arguments'
        return 2
    fi

    local -r dst=$1 src=$2 rev=${3-HEAD}
    local -r svn=("$svn" --non-interactive)
    local -r root=$("${svn[@]}" info --show-item wc-root "$dst" 2>/dev/null)

    if [[ -z $root ]] && is-empty "$dst"; then
        printf "\n---> Checking out Subversion repository from \`%s'\n" "$src"
        # "svn checkout" creates the intermediate directories.
        "${svn[@]}" checkout --revision "$rev" "$src" "$dst"
    elif [[ $root -ef $dst ]]; then
        # TODO Allow switching the Subversion server.
        printf "\n---> Updating Subversion repository from \`%s'\n" \
            "$("${svn[@]}" info --show-item url "$dst")"
        "${svn[@]}" cleanup "$dst" \
            && "${svn[@]}" update --revision "$rev" "$dst"
    else
        err "\`$dst' is not an empty directory or" \
            'the root of a Subversion working copy'
        return 1
    fi
}

checkout() {
    local args
    parseopt git::,jobs-url:,ports-branch:,ports-commit:,ports-url:,svn::,svn-url: "$@" \
        || return
    # shellcheck disable=SC2086
    set -- ${args+"${args[@]}"}

    # shellcheck disable=SC2154
    # To maintain backwards compatibility, --svn-url implies --svn.
    if [[ -n ${option_svn_url+_} ]]; then
        : "${option_svn=}"
    fi

    # shellcheck disable=SC2154
    local -r ports_dir=${option_work_dir}/ports

    local checkout git jobs_dir svn
    # shellcheck disable=SC2100 disable=SC2154
    if [[ -z ${option_svn+_} ]] && git=${option_git:-$(command -v git)}; then
        checkout=git-checkout
        # Checking out "jobs_url" should create a "jobs" subdirectory.
        jobs_dir=$(dirname "${option_jobs_dir}")
        : "${option_jobs_url=https://github.com/macports/macports-infrastructure.git}"
        : "${option_ports_url=https://github.com/macports/macports-ports.git}"
    elif [[ -z ${option_git+_} ]] && svn=${option_svn:-$(command -v svn)}; then
        checkout=svn-checkout
        jobs_dir=${option_jobs_dir}
        if [[ -n ${option_svn_url+_} ]]; then
            : "${option_jobs_url=${option_svn_url}/base/portmgr/jobs}"
            : "${option_ports_url=${option_svn_url}/dports}"
        else
            : "${option_jobs_url=https://github.com/macports/macports-infrastructure.git/trunk/jobs}"
            : "${option_ports_url=https://github.com/macports/macports-ports.git/trunk}"
        fi
    else
        case ${option_git+_},${option_svn+_} in
            _,_) err 'cannot use both Git and Subversion'; return 2 ;;
            _,)  err 'cannot find a Git client' ;;
            ,_)  err 'cannot find a Subversion client' ;;
            ,)   err 'cannot find any Git or Subversion clients' ;;
        esac
        return 1
    fi
    readonly checkout git jobs_dir svn

    $checkout "${jobs_dir}" "${option_jobs_url}" || return
    # shellcheck disable=SC2086 disable=SC2154
    $checkout "${ports_dir}" "${option_ports_url}" \
            ${option_ports_commit+"${option_ports_commit}"} \
            ${option_ports_branch+"${option_ports_branch}"} || return

    # $option_prefix is set in mpbb
    # shellcheck disable=SC2154
    (cd "${ports_dir}" && "${option_prefix}/bin/portindex") || return

    local -ar mirrors=(aarnet.au cjj.kr fco.it her.gr jnb.za jog.id
                       lil.fr mse.uk nou.nc nue.de osl.no sea.us ykf.ca)
    # Unset IFS to ensure "${mirrors[*]}" uses spaces as separators.
    (unset IFS && cat > "${option_work_dir}/macports.conf" <<EOF
# Automatically overwritten by mpbb-checkout
# Do not edit !!!
sources_conf ${option_work_dir}/sources.conf
host_blacklist ${mirrors[*]/%/.distfiles.macports.org} \
               ${mirrors[*]/%/.packages.macports.org}
EOF
) || return

    cat > "${option_work_dir}/sources.conf" <<EOF || return
# Automatically overwritten by mpbb-checkout
# Do not edit !!!
file://${ports_dir} [default]
EOF

}