#!/bin/sh

# Download pre-built webpack for current git SHA from GitHub

# These are produced by .github/workflows/build-dist.yml for every PR and push.
# This is a lot faster than having to npm install and run webpack.

# Returns 0 when successful, 1 in case of an error, or 2 in case the cache
# entry couldn't be found (but might be available after waiting a bit longer).

GITHUB_REPO='cockpit-dist'
SUBDIR='dist'

export V="${V-0}"

set -eu
cd "$(realpath -m "$0"/../..)"
. tools/git-utils.sh

wait=''
rebase=''
partial=''
while [ $# != 0 ] ; do
    case "$1" in
        --wait)
            wait=1;;
        --rebase)
            rebase=1;;
        --partial)
            partial=1;;
        *)
            echo "usage: $0 [--rebase] [--wait]" >&2
            exit 1
    esac
    shift
done

[ -n "${quiet}" ] || set -x

if [ -e dist ]; then
    echo "jumpstart: dist/ already exists, skipping" >&2
    exit 1
fi

if [ "${NODE_ENV-}" = "development" ]; then
    echo 'jumpstart: only works with production builds (NODE_ENV != development)' >&2
    exit 1
fi

# Here we find "base commit": the commit in our local tree which is equal to
# the HEAD of the PR at the time the jumpstart was built.  This might not be
# the commit that the jumpstart was actually built against (in case the PR
# wasn't fully-rebased and GitHub merged some commits from main) and it might
# also not be the current local HEAD (in case local changes were added after.
# --rebase and --partial help us deal with those situations.
#
# For example:
#
#                        o---L---l           (local tree at start)
#                       /
#              a---b---H                     (PR head)
#             /        |\
#            /       --M \---N               (GitHub PR merge commits)
#           /       /       /
#   ---o---o---o---m---o---n                 (origin/main)
#                   \
#                    a'--b'--H'--L'--l'      (rebased result)
#
# We might be run from H, M, N, L or l.
#
# Someone has proposed a PR with three commits ('a', 'b', and head 'H'),
# branched off of main, which had two additional commits (up to head 'm') at
# the time of the jumpstart build.  GitHub will have created merge commit 'M'
# for the jumpstart build, but it will be marked with the tag `sha-H` so that
# it's possible for us to find it.  The jumpstart bundle will have a note of
# commit `m` in the contained `merge-base` file so that we can reconstruct the
# tree state of `M` for ourselves.  That's the idea of `--rebase`: we will
# rebase, adding commits a', b', and H' on top of m.  Assuming merging and
# rebasing produced the same result (which it almost always does), the tree
# state of H' will be exactly the same as M.
#
# We might also be asked to run against the merge commit ('M') itself, or a new
# merge commit ('N') in case main received additional commits.  In either case,
# the best thing we can do in either case is to back up to the original head
# 'H' and rebase onto 'm' from there, as above.
#
# Finally, it might be that our local tree was originally based on the HEAD
# commit that was built for the jumpstart, but has additionally gained extra
# local commits (head 'L') and/or uncommitted changes ('l').  In this case, we
# need to search through our own history to find the latest commit for which a
# jumpstart was built (ie: 'H'), and then after applying that jumpstart, touch
# the files that were changed, in order to make sure everything is properly
# rebuilt.  That's --partial.

if test -n "${partial}"; then
    tag=""
    # fetch the available commits, then iterate our history looking for the first match
    available="$(git_cache ls-remote origin 'refs/tags/sha-*' | cut -f2 -d-)"
    for commit in $(git log -n 100 --format=%H); do
        if [ "${available%${commit}*}" != "${available}" ]; then
            tag="sha-${commit}"
            break
        fi
    done
    if [ -z "${tag}" ]; then
        echo "Unable to find any suitable commit" >&2
        exit 1
    fi
else
    if [ -n "$(git status --porcelain)" ]; then
        echo 'Refusing operation on an unclean tree: try --partial' >&2
        echo
        git status >&2
        exit 1
    fi
    # If we are rebasing and the HEAD commit is a merge, reset to the
    # merged commit before proceeding.
    if test -n "${rebase}" && git rev-parse --verify HEAD^2 >/dev/null; then
        git reset --hard HEAD^2
    fi
    tag="sha-$(git rev-parse HEAD)"
fi

for try in $(seq 50 -1 0); do
    if fetch_to_cache tag "${tag}"; then
        break
    fi
    if [ -z "${wait}" -o "$try" = '0' ]; then
        echo "There is no cache entry ${tag}" >&2
        exit 1
    fi
    message WAIT 30s
    sleep 30s
done

if [ -n "${rebase}" ]; then
    merge_base="$(cat_from_cache "${tag}" merge-base)"
    # If we don't already have that commit, we'll need to go fetch it
    if ! git rev-list --quiet --objects "${merge_base}"; then
        message FETCH ".  [${merge_base}]"
        git fetch --no-write-fetch-head origin "${merge_base}"
    fi
    git rebase --autostash -- "${merge_base}"
fi

target_tree="$(cat_from_cache "${tag}" tree)"
changed_files="$(git diff --name-only "${target_tree}")"
if [ -n "${changed_files}" -a -z "${partial}" ]; then
    if [ -n "${rebase-}" ]; then
        echo "Internal error: even after rebase, don't have the correct tree" >&2
    else
        echo "The current working tree needs to be rebased: try --rebase" >&2
    fi
    exit 1
fi

tools/node-modules make_package_lock_json
unpack_from_cache "${tag}"

if [ -n "${changed_files}" ]; then
    printf "\nTouching the following changed files:\n%s\n\n" "${changed_files}" >&2
    touch -- ${changed_files}
fi
