#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
#
# git-archive-signer
# ------------------
# Use this script to create a tarball signature for a tag and store
# it as part of the repo (using git notes, as supported by cgit).

# Don't change this if you want this to actually work
NOTEREF="refs/notes/signatures/tar"
NOTEREF_MINISIG="refs/notes/minisig/tar"

# Pass the tag as the only parameter, otherwise we grab the latest
# annotated tag we find. You may also pass "list" to list all tags that
# already carry corresponding signature notes.
if [[ $1 == "list" ]]; then
    git notes --ref ${NOTEREF} list | cut -d' ' -f2 | xargs git describe
    exit 0
fi

TAG=$1

# Set this to your gitolite.kernel.org remote
# We'll also use git config --get archive-signer.remote if we find it
REMOTE="$(git config --get archive-signer.remote)"
if [[ -z ${REMOTE} ]]; then
    REMOTE="origin"
fi

# Change this if your gpg2 is elsewhere
GPGBIN="/usr/bin/gpg2"
# Change this if your minisign is elsewhere
MINISIGNBIN="/usr/bin/minisign"

# If you want to use a specific key (or subkey) instead of the default,
# then edit and uncomment this line. If you have multiple valid signing
# subkeys, then add the exact subkey ID and add a "!" at the end.
# We'll also use git config archive-signer.usekey value if we find it
#USEKEY="Ox12345678DEADBEEF"
USEKEY="$(git config --get archive-signer.usekey)"

# We use TARNAME when making a git archive --prefix, e.g. $TARNAME-1.2.3/
# Set it here if guessing basename is wrong.
# We'll also use git config archive-signer.tarname value if we find it
TARNAME="$(git config --get archive-signer.tarname)"
if [[ -z ${TARNAME} ]]; then
    TARNAME="$(basename $(pwd))"
fi

# You shouldn't need to change anything below

if [[ -z ${TAG} ]]; then
    # Assume you want the latest tag
    TAG="$(git describe --abbrev=0)"
    if [[ -z ${TAG} ]]; then
        echo "Could not figure out which tag you want"
        exit 1
    fi
fi

# The archive prefix will be created as $TARNAME-$TAG/ (sans leading v)
# Change it here if it doesn't correspond to your needs
# We'll add the trailing / where it is needed, so don't add it here
PREFIX="${TARNAME}-${TAG#v}"

# Do we already have a signature note for this tag?
# Start by fetching the origin notes
echo "Updating notes from remote"
git fetch ${REMOTE} "refs/notes/*:refs/notes/*"
if git notes --ref=${NOTEREF} list ${TAG} >/dev/null 2>&1; then
    echo "Signature note for ${TAG} already exists!"
    echo "To make a new one, delete it first:"
    echo "    git notes --ref=${NOTEREF} remove ${TAG}"
    exit 1
fi

echo -n "Generate signature note for ${PREFIX}.tar? [Y/n] "
read YN

[[ -z ${YN} ]] && YN=y
[[ ${YN} != "y" ]] && exit 1

# We add the exact archive line to sig comments,
# so put it together here
GIT_ARCHIVE_CMD="git archive --format tar --prefix=${PREFIX}/ ${TAG}"
# Record the version of git that created this archive
GIT_VERSION=$(git --version)

if [[ ! -z ${USEKEY} ]]; then
    GPGBIN="${GPGBIN} -u ${USEKEY}"
fi

# We put the tarball into a temp file, in case we need to minisign it, too
TMP_ARCHIVE=$(mktemp)
echo -n "Running ${GIT_ARCHIVE_CMD}..."
${GIT_ARCHIVE_CMD} > ${TMP_ARCHIVE}
echo "done"
git notes --ref=${NOTEREF} add -C "$(
    cat ${TMP_ARCHIVE} | ${GPGBIN} -a -b -o - \
        --comment "This signature is for the .tar version of the archive" \
        --comment "${GIT_ARCHIVE_CMD}" \
        --comment "${GIT_VERSION}" |
        git hash-object -w --stdin)" "${TAG}"

if [[ $? != 0 ]]; then
    echo "git notes exited with error"
    rm -f ${TMP_ARCHIVE}
    exit 1
fi

echo
git --no-pager notes --ref=${NOTEREF} show ${TAG}
echo

USE_MINISIGN="$(git config --get archive-signer.use-minisign)"
if [[ ${USE_MINISIGN} == "yes" ]]; then
    if git notes --ref=${NOTEREF_MINISIG} list ${TAG} >/dev/null 2>&1; then
        echo "Minisign note for ${TAG} already exists!"
        echo "To make a new one, delete it first:"
        echo "    git notes --ref=${NOTEREF_MINISIG} remove ${TAG}"
        exit 1
    fi
    MINISIGN_CMD="${MINISIGNBIN}"
    MINISIGN_COMMENT="This minisign signature is for the .tar version of the archive"
    MINISIGN_TRUSTED="$(date -u), ${GIT_VERSION}, using: ${GIT_ARCHIVE_CMD}"
    # If minisign-keyfile is set, we'll use that key instead of the default
    MINISIGN_KEY="$(git config --get archive-signer.minisign-key)"
    if [[ ! -z ${MINISIGN_KEY} ]]; then
        MINISIGN_CMD="${MINISIGN_CMD} -s $(eval echo ${MINISIGN_KEY})"
    fi
    # If you don't want to type in the minisign passphrase, you can
    # store it gpg-encrypted and set archive.signer.minisign-gpg-passphrase to
    # point at the file containing the encrypted passphrase.
    # To generate, use:
    #     echo passphrase | gpg -r YOURKEYID -e > minisign-passphrase.gpg
    MINISIGN_PASSPHRASE="$(git config --get archive-signer.minisign-gpg-passphrase)"
    MINISIGN_OUT=$(mktemp)
    echo "Generating minisign signature"
    if [[ -z ${MINISIGN_PASSPHRASE} ]]; then
        ${MINISIGN_CMD} -S \
            -c "${MINISIGN_COMMENT}" -t "${MINISIGN_TRUSTED}" \
            -x ${MINISIGN_OUT} -m ${TMP_ARCHIVE}
    else
        echo "Using the gpg-encrypted passphrase from ${MINISIGN_PASSPHRASE}"
        ${GPGBIN} -q -d $(eval echo ${MINISIGN_PASSPHRASE}) \
            | ${MINISIGN_CMD} -S \
                -c "${MINISIGN_COMMENT}" -t "${MINISIGN_TRUSTED}" \
                -x ${MINISIGN_OUT} -m ${TMP_ARCHIVE}
    fi
    if [[ ! -s ${MINISIGN_OUT} ]]; then
        # Assume minisign process went wrong
        echo "Minisign signature is missing, aborting!"
        rm -f ${TMP_ARCHIVE}
        exit 1
    fi
    git notes --ref=${NOTEREF_MINISIG} add \
        -C "$(cat ${MINISIGN_OUT} | git hash-object -w --stdin)" "${TAG}"

    if [[ $? != 0 ]]; then
        echo "git notes exited with error"
        rm -f ${TMP_ARCHIVE} ${MINISIGN_OUT}
        exit 1
    fi

    echo "-----"
    git --no-pager notes --ref=${NOTEREF_MINISIG} show ${TAG}
    echo "-----"
    echo

    rm -f ${MINISIGN_OUT}
fi

rm -f ${TMP_ARCHIVE}

echo -n "Push to ${REMOTE}? [Y/n] "
read YN
echo

[[ -z ${YN} ]] && YN=y
if [[ ${YN} != "y" ]]; then
    echo "Remember to push it using:"
    echo "    git push ${REMOTE} refs/notes/*"
    exit 0
fi

echo "Pushing notes to ${REMOTE}..."
git push ${REMOTE} refs/notes/*
