#!/bin/sh -efu
#
# The ALT checksum repository tool.
#
# Copyright (C) 2024  Paul Wolneykien.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

PROG="${0##*/}"
VERSION="0.1.7"
YEAR="2025"

REMOTE="$PROG"
PWD="${PWD:-$(pwd)}"

CMD="$0"

has_git() {
    [ -d "${GIT_DIR:-.git}" ]
}

has_remote() {
    has_git || return 1
    git remote | grep -q "^$REMOTE\$"
}

verb() {
    (
	set -x
	"$@"
    )
}

find_gpg() {
    if ! GPGCMD="$(git config gpg.program)"; then
	if ! GPGCMD="$(which gpg)"; then
	    if ! GPGCMD="$(which gpg2)"; then
		echo "ERROR: Please, install GnuPG (ver. 1 or 2) or configure the installed version with \`git config gpg.program\`." >&2
		return 1
	    fi
	fi
    fi
}

with_keys() {
    (
	trap '[ -z "${workdir:-}" ] || rm -rf "$workdir"' EXIT
	workdir="$(mktemp -d --tmpdir "$PROG.XXXX")"
	export GNUPGHOME="$workdir"
	cat <<EOF >"$workdir"/gpg.conf
no-greeting
lock-never
always-trust
no-secmem-warning
quiet
EOF
	find_gpg
	"$CMD" keys | "$GPGCMD" --import
	"$@"
    )
}

case "${1:-}" in
    version)
	cat <<EOF
$PROG $VERSION $YEAR
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
EOF
	exit 0
	;;
    init)
	! has_git || exit 0
	verb git init ${2:+"$2"}
	;;
    url)
	case "${2:-}" in
	    '')
		if has_git && has_remote; then
		    git remote get-url "$REMOTE"
		else
		    "$CMD" url \? | head -1 | cut -f2 -d' '
		fi
		;;
	    \?)
		cat <<EOF
1) https://checksum.altsp.su/alt-checksum/checksums.git
2) https://gitlab.basealt.space/alt-checksum/checksums.git
EOF
		;;
	    [0-9]*)
		(
		    url_n="$("$CMD" url \? | tail -n +"$2" | head -1 | cut -f2 -d' ')"
		    if [ -n "$url_n" ]; then
			"$CMD" url "$url_n"
		    else
			echo "No preconfigured URL number $2. Type '$PROG url \?' to display list of known URLs." >&2
			exit 1
		    fi
		)
		;;
	    *)
		has_git || "$CMD" init
		if has_remote; then
		    verb git remote set-url "$REMOTE" "$2"
		    verb git remote prune "$REMOTE"
		else
		    verb git remote add "$REMOTE" "$2"
		fi
		;;
	esac
	;;
    add)
	case "${2:-}" in
	    \?)
		git ls-remote -q --refs --heads "$("$CMD" url)" | \
		    while read -r _ path; do
			echo "${path#refs/heads/}"
		    done
		;;
	    '')
		"$CMD" help add >&2
		exit 1
		;;
	    *)
		has_remote || "$CMD" url "$("$CMD" url)"
		verb git fetch -pP "$REMOTE" refs/heads/"$2":"$2"
		git branch -u "$REMOTE"/"$2" "$2"
		verb mkdir -p "$2"
		verb git worktree add "$2" "$2"
		verb "$CMD" validate "$2"
		;;
	esac
	;;
    show)
	has_remote || exit 0
	case "${2:-}" in
	    \?)
		git branch --list --format='%(upstream:remotename) %(refname:short) %(worktreepath)' | grep "^$REMOTE " | \
		    while read -r _ name path; do
			case "$path" in
			    "$PWD"/*)
				echo "$name"
				;;
			esac
		    done
		;;
	    '')
		"$CMD" help show >&2
		exit 1
		;;
	    *)
		git log --format=full -1 refs/heads/"$2" | cat
		;;
	esac
	;;
    log)
	has_remote || exit 0
	case "${2:-}" in
	    \?)
		"$CMD" show \?
		;;
	    '')
		"$CMD" help log >&2
		exit 1
		;;
	    *)
		git log refs/heads/"$2"
		;;
	esac
	;;
    del)
	has_remote || exit 0
	case "${2:-}" in
	    \?)
		"$CMD" show \?
		;;
	    '')
		"$CMD" help del >&2
		exit 1
		;;
	    *)
		verb git worktree remove --force "$2"
		verb git branch -D "$2"
		verb git gc
		;;
	esac
	;;
    update)
	has_remote || exit 0
	case "${2:-}" in
	    \?)
		"$CMD" show \?
		;;
	    '')
		"$CMD" update \? | while read -r br; do
		    "$CMD" update "$br"
		done
		;;
	    *)
		(
		    set -x
		    cd "$2"
		    git pull --ff-only "$REMOTE" "$2"
		)
		verb "$CMD" validate "$2"
		;;
	esac
	;;	
    keys)
	case "${2:-}" in
	    \?)
		find_gpg
		with_keys verb "$GPGCMD" --list-keys
		;;
	    '')
		cat <<EOF
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBGkWJYgBEAD3i5LWH+P5+iRkRjf5ozGatFN5Rqh4wLeR2P11e9/vnzYLhnkf
d18Be4pTcBvZK6qa88y9RpOe8zPqM04ZFYhwL6XgG43rHVX6cmJ8n55NPSmCCviB
fpAb3MYZAifWTUGKruyfHmdyQtjQ3P7jJpvXOjdFvEP7u37KKc2HOhL1h3tXSsVe
KStAGQkYqlEZcUJWjMp7agTGYdOn5VDCsX7I2EdwCN7KWegs9DW99eTiSkRBiRmV
1CJpOvP+XZy+cy8psGAeZlEbT/TgMtc00H/miqklaLrChXtozh8ordpF1znleolD
OxdVD0XOVXZCxSrxxba5f4Xi0PU3iSFFSXtGBGPYR5hfUROw6S4Tzv5QoPlhcmcZ
TJJ0tyHDFdLSRWzjz/0fswWzrKEHTlrfgQmjXzcVZUXek1bGqKZ7VNrEqsYkAabM
wBleZl456Q1DLX7lJMWvmph0oKQno1yAdTf4db8SseASvXL/P941BgWwRRVX+uaJ
idp2K0WuhjzL8PQFbRB7Z0QjrGz2H7tC6PsDxb4saxuGgD75FHSEVh/EUsHYgeE6
p9484/NqbXTUO4ayvQkSR9sSjf4PWw3Ps6cU8eorF57RUxnTYR9Kxhww41ycu0Q1
MtGYL4ItdhS0U5d85suatkkr/2BEpJ/dGZvaJn4E3oKF7GhOIykX1wrePwARAQAB
tEdDaGUgWHVuZyBCaG90IChSUE0gY2hlY2tzdW0gZ2VuZXJhdG9yIHJvYm90KSA8
Y2hlY2tzdW1ib3RAYWx0bGludXgub3JnPokCVwQTAQgAQRYhBAn+jXYGhFJ/gN3q
wZ2km/H+fjaCBQJpFiWIAhsDBQkB4TOABQsJCAcCAiICBhUKCQgLAgQWAgMBAh4H
AheAAAoJEJ2km/H+fjaCRHYQAPVLrD/sTCLrl0mP668iUA/uayG36338p1srFS8O
qekQ71O2D+LK9BZeFBCm2NCvrFbIBbeXcA5wBBrkduxJ1V73fu0Grw03G8WLyY49
XDaXMjhRkB2kxUyv6U6ePGQwTet8TKKncukOk3UEMZtpoxQDEN0Xa1hWU4MfUmry
z28gtbgehuh8Ul+JKc4moLcjqa+qijlAjwHD8W4+bPInE44KbtInnp2z8cPUQZk/
9HRPHMRDXicIRIOKGPyRs6Im492pBj0aVJjWAEBsCS6yFWyDqX31blfKjCCWEJdH
mx4wnlwY0JhtzkXYaQHP4AnI6TZFO6KOWNy7HQcZmpdGvw6nFvFQxM3Zb+VP+Aly
monIvANVSbg5yseYUZNi7EUDMDUu2+lg68b9KVjD+G1c6Rxa/+LnuNCPQ+v8M6OR
3Gsf2fqwXtqNXzQ5vplDP8/uH7dN6jk6gf99fwLjU/faryfPEgSgGJD9pQaYwyx7
wq49ehnLo0AaqXCZHVUIbPsv1iVzUqALgefO9gxg/bWbU6Nk/a9pkDSVlMHlO07/
jUIWYXPzut07NopOMFWjuTkuI80Z2AIFoO10B/ZGMMSyKgGMY2caZTb5aWey7LX/
Vl53cP4/b5Y40GvQIm5XuGBqjtfKdfkCn7Ywq5CmUbEZJP4JWi28Zy26VLmbDPKA
6hkq
=V0fF
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PUBLIC KEY BLOCK-----

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
fH2EaRXn9RrE+aS4dgg=
=NOFL
-----END PGP PUBLIC KEY BLOCK-----
EOF
		;;
	    *)
		"$CMD" help keys >&2
		exit 1
		;;
	esac
	;;
    validate)
	case "${2:-}" in
	    \?)
		"$CMD" show \?
		;;
	    '')
		if ! has_remote; then
		    echo 'ERROR: Uninitialized checksum repository.' >&2
		    exit 1
		fi

		if [ -z "$("$CMD" validate \?)" ]; then
		    echo "ERROR: Nothing to validate! Add some branches with \`$PROG add\`" >&2
		    exit 1
		fi

		"$CMD" validate \? | while read -r br; do
		    "$CMD" validate "$br" || exit $?
		done
		;;
	    *)
		(
		    cd "$2"
		    if [ -n "$(verb git status -unormal --porcelain)" ]
		    then
			git status -unormal
			echo "ERROR: Untracked files and/or not-committed changes found in $2." >&2
			exit 1
		    fi

		    tag="$(git tag --points-at refs/heads/"$2")"

		    case "$(git log -1 --format='%G?' HEAD)" in
			N)
			    if [ -z "$tag" ]; then
				echo "ERROR: No signed tag found for the unsigned HEAD commit in $2." >&2
				exit 1
			    fi
			    with_keys verb git tag -v "$tag"
			    ;;
			*)
			    with_keys verb git verify-commit -v HEAD
			    [ -z "$tag" ] || with_keys verb git tag -v "$tag"
			    ;;
		    esac

		    if [ -s ./EOL.txt ]; then
			cat ./EOL.txt
		    fi
		)
		;;
	esac
	;;
    verify)
	shift
	(
	    dirs="$("$0" show \?)"
	    if [ -z "$dirs" ]; then
		echo "ERROR: No branches yet! Add some branches with \`$PROG add\`" >&2
		exit 1
	    fi
	    #shellcheck disable=SC2046
	    verb verify-checksums $(for d in $dirs; do echo --dir "$d"; done) "$@"
	)
	;;
    help)
	case "${2:-}" in
	    url)
		cat <<EOF
Usage: $PROG url [\? | <n> | https://...]
\?       -- display preconfigured enumerated set of URLs;
<n>      -- select an URL with the given number;
https:// -- select the given (custom) URL.
EOF
		;;
	    add)
		cat <<EOF
Usage: $PROG add \? | <branch>
\?       -- list branches available for the selected URL;
<branch> -- download and checkout a branch with the given name.
EOF
		;;
	    show)
		cat <<EOF
Usage: $PROG show \? | <branch>
\?       -- list the currently checked-out branches;
<branch> -- display detailed information about the branch.
EOF
		;;
	    log)
		cat <<EOF
Usage: $PROG log \? | <branch>
\?       -- list the currently checked-out branches;
<branch> -- display log of the given branch.
EOF
		;;
	    del)
		cat <<EOF
Usage: $PROG del \? | <branch>
\?       -- list the currently checked-out branches;
<branch> -- remove the local copy of the branch.
EOF
		;;
	    update)
		cat <<EOF
Usage: $PROG update [\? | <branch>]
\?       -- list the currently checked-out branches;
<branch> -- download updates for the given branch;
         -- or for all currently checked-out branches.
EOF
		;;
	    validate)
		cat <<EOF
Usage: $PROG validate [ \? | <branch>]
\?       -- list the currently checked-out branches;
<branch> -- validate the signature of the given branch;
         -- or for all currently checked-out branches.
EOF
		;;
	    verify)
		cat <<EOF
Usage: $PROG verify [...options to verification script]
... -- all options are passed to the verify-checksum(1) script.
EOF
		;;
	    keys)
		cat <<EOF
Usage: $PROG keys [\?]
\? -- display metadata of available public keys;
   -- or dump the key data as is.
EOF
		;;
	    init)
		cat <<EOF
Usage: $PROG init [path/to/dir...]
path/to/dir... -- initialize the given directory for checksum data;
               -- otherwise, initialize the current directory.
EOF
		;;
	    help)
		cat <<EOF
Usage: $PROG help [<command>]
<command> -- display usage information on the given command;
          -- or the general usage information.
EOF
		;;
	    version)
		cat <<EOF
Usage: $PROG version
display $PROG version and license information and exit.
EOF
		;;
	    *)
		cat <<EOF
Usage: $PROG <command> ...
$PROG url [\? | <n> | https://...]
$PROG add \? | <branch>
$PROG show \? | <branch>
$PROG log \? | <branch>
$PROG del \? | <branch>
$PROG update [\? | <branch>]
$PROG validate [ \? | <branch>]
$PROG verify [...options to verification script]
$PROG keys [\?]
$PROG init [path/to/dir...]
$PROG help [<command>]
$PROG version
EOF
		;;
	esac
	;;
    *)
	"$CMD" help >&2
	exit 1
	;;
esac
