#!/bin/sh
export LC_ALL=C

if [ x"${1:-}" != x"--skip-selfupdate" ]; then
    # try self-update of this repo (don't abort on failure) and re-run.
    (set -ex && cd "$(dirname "$0")" && git pull -r) || echo "Warning: cannot self-update"
    exec "$0" --skip-selfupdate "$@"
fi
shift

cd "$(dirname "$0")"
config_process_projects_cli=yes
. scripts/core-config || exit 1

# the remote name we set and use - we don't touch "origin" or "upstream" etc.
remote=$config_main-build-remote

# $1: tag prefix
versort_with_prefix() {
    # Emulate sort -V using a known prefix. Filter out anything else.
    sed -n -e "s/^$1\([0-9]\)/\\1/p" |\
        sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 |\
        sed -e "s/^/$1/"
    # GNU version of the same:
    # grep "^$2[0-9]" | sort -V
}

remote_tags() {
    # tags ending with ^{} are refs to other tags with the same name sans ^{}
    git ls-remote --tags $remote | cut -f2 | grep -v '\^{}$' | sed "s/^refs\/tags\///g"
}

# $1: tag prefix
highest_release_tag_ref() {
    local tag="$(remote_tags | grep -v rc | grep -v dev | versort_with_prefix "$1" | tail -n 1)"
    [ $? = 0 ] && [ "$tag" ] || return 1
    printf %s "refs/tags/$tag"
}

# $1: repo URL, $2: repo dir [,$3: non empty if shallow update]
ensure_repo() { (
    if [ -e "$2" ]; then
        cd "$2" &&
        { git config remote.$remote.url > /dev/null || git remote add $remote "$1"; } &&
        git remote set-url $remote "$1"
    else
        if [ -z "${3-}" ]; then
            # not shallow -> plain clone
            git clone "$1" "$2" &&
            cd "$2" &&
            git remote add $remote "$1"
        else
            # shallow -> init the repo without fetching anything
            mkdir -p "$2" &&
            cd "$2" &&
            git init &&
            git remote add origin "$1" &&
            git remote add $remote "$1"
        fi
    fi
) || err_msg_die "cannot setup git repo '$2' at '$3'"; }

# resolve a full remote ref from [fuzzy] $1, or fail
remote_ref() (
    set +x
    refs="$(git ls-remote $remote | cut -f2 | grep -v '\^{}$')" || exit 1

    # we only know to handle HEAD or refs/*
    [ HEAD = "$1" ] && printf HEAD ||
    (printf %s "$refs" | grep "^refs/${1#refs/}\$") ||
    (printf %s "$refs" | grep "^refs/tags/$1\$") ||
    (printf %s "$refs" | grep "^refs/heads/$1\$") ||
    (printf %s "$refs" | grep "^refs/pull/\(${1#PR}\|${1#\#}\)/head\$")  # PR42, #42, 42
)

# $1: remote ref, [$2 ... : earlier fetch args]
# tries to create a local reference for known types (tags/heads/pull) such that
# subsequent fetches may fetch only delta, else checkout the temp FETCH_HEAD
fetch_checkout() (
    f="$1"        # default fetch ref arg - without a local ref
    c=FETCH_HEAD  # default checkout arg
    shift

    case "$f" in
        # create local reference using remote-ref:local-ref syntax.
        refs/tags/*)  c="$f"                                   && f="$f:$c";;
        refs/heads/*) c="refs/remotes/$remote/${f#refs/heads/}" && f="$f:$c";;
        refs/pull/*)  c="refs/remotes/$remote/${f#refs/}"       && f="$f:$c";;
        HEAD)         c="refs/remotes/$remote/HEAD"             && f="$f:$c";;
    esac
    git fetch --force "$@" $remote "$f" && git checkout "$c"^0
)

# succeeds if $1 dir is a shallow git repo
is_shallow() (
    cd "$1" &&
    case "$(git rev-parse --is-shallow-repository)" in
         true) true;;
        false) false;;
            *) gdir="$(git rev-parse --git-dir)" && [ -e "$gdir"/shallow ]
    esac
)

# $1: update value, $2: repo URL, $3: repo dir, $4: release prefix
update_source_or_die() { (
    set +x

    ref=
    update="$1"
    depth=
    [ yes = "$config_git_shallow" ] && depth="--depth=$config_git_shallow_depth"
    # if update starts with '-' or '+' strip it and set $depth accordingly
    case "$update" in
        -*) update="${update#-}"
            depth="--depth=$config_git_shallow_depth"
            ;;
        +*) update="${update#+}"
            depth=
            ;;
    esac

    # if the dir exists and isn't a shalow repo - don't use --depth=...
    { ! [ -e "$3" ]; } || is_shallow "$3" || depth=

    set -x
    ensure_repo "$2" "$3" "$depth" &&
    cd "$3" &&
    case "$update" in
            @*) git fetch $depth && git checkout "${update#@}";;
        rebase) git pull $depth --rebase;;
          head) fetch_checkout HEAD $depth;;

       release) ref="$(highest_release_tag_ref "$4")" ||
                    err_msg_die "cannot find release tags with prefix '$4'"
                fetch_checkout "$ref" $depth
                ;;

            ^*) ref="$(remote_ref "${update#^}")" ||
                    err_msg_die "cannot find remote ref '${update#^}'"
                fetch_checkout "$ref" $depth
                ;;

            *) err_msg_die "Unknown update type '$1'"
    esac
) || err_msg_die "update failed"; }

hook_or_die "$config_update_pre" update-pre

for p in $config_projects; do
    update="$(var_val config_${p}_update)"
    highlight_msg "($config_projects) updating $p/$update"

    case "$update" in
        none) continue;;
          !*) hook_or_die "${update#!}" $p;;
           *) update_source_or_die  "$update" \
                "$(var_val config_${p}_git)" \
                "$(var_val config_${p}_source)" \
                "$(var_val config_${p}_release_prefix)"
    esac

    hook_or_die "$(var_val config_${p}_update_post)" ${p}-update-post
done

scripts/debian-update-versions || err_msg_die "cannnot update debian versions"

hook_or_die "$config_update_post" update-post
success_msg "updated: $config_projects"
