derive-deftly 1.3.0

An ergonomic way to write derive() macros
Documentation
#!/bin/bash
#
# We fail CI if the word "F I X M E" is detedcted, as a whole word,
# in any case.  The word is not matched if there are word-characters
# abutted to it.

set -e
set -o pipefail

BASE_BRANCHES='main'

repo=$(
    perl -MTOML -we '
        use strict;
        undef $/;
        my $toml = from_toml(<STDIN>);
        print $toml->{package}{repository} // die;
    ' <Cargo.toml
)
repo_host=$(
    perl -we '
        $ARGV[0] =~ m{^https://[-.0-9a-z]+/}
            or die "bad repository url";
        print "$&\n";
    ' "$repo"
)

echo "Cargo.toml package.repository: $repo"
echo "Upstream repository instance: $repo_host"

# Ideally we would somehow find which repo and branch this was going
# to be an MR for.  But, in fact, we can't even find out which
# repo this repo it was forked from, in a principled way, with env vars.
# This approach to finding the upstream will have to do for now.
case "$CI_PROJECT_URL" in
    "$repo_host"*)
	# `package.repository` in `Cargo.toml` is
	# on same instance as this CI job.
	#
	# Use $BASE_BRANCHES on `package.repository` as the baseline.
	git remote rm upstream.tmp >/dev/null 2>&1 ||:
	git remote rename upstream upstream.tmp >/dev/null 2>&1 ||:
	git remote add upstream "$repo"
	ORIGIN=upstream
	;;
    '')
	# Not running in CI, use a guess at the baseline:
	# $BASE_BRANCHES at whatever `origin` points to.
	ORIGIN=origin
	;;
    *)
	# We are apparently running in a different instance to upstream.
	# It's not clear what this testing would mean.
	# Should it use our repo URL?  That would be quite exciting.
	echo >&2 "CI_PROJECT_URL $CI_PROJECT_URL not recognised!"
	exit 4
	;;
esac

echo "Baseline repo remote $ORIGIN, at:"
git remote get-url "$ORIGIN"

tlref () {
    echo "refs/remotes/$ORIGIN/$tbranch"
}

x () {
    echo "+ $*"
    "$@"
}

git_checkout_maybe_clean () {
    x=$1; shift
    if
	$x git checkout -q "$1"
    then :
    else 
	echo "Checkout failed, trying with git clean and git reset"
	x git clean -xdff
	x git reset --hard
	x git checkout -q "$1"
    fi
}

old_branch=$(git symbolic-ref -q HEAD || test $? = 1)

restore_old_branch () {
    case "$old_branch" in
	refs/heads/*)
	    git_checkout_maybe_clean '' "${old_branch#refs/heads/}" ||
		echo '*** Failed to return to original branch ***'
	    ;;
	*)
	    echo "*** Was not originally on a branch, HEAD changed! ***"
	    ;;
    esac
}

trap 'restore_old_branch' 0

refspecs=()
for tbranch in $BASE_BRANCHES; do
    refspecs+=(+"refs/heads/$tbranch:$(tlref)")
done

# If you try to unshallow a repo that's already complete, you get
# an error!  Hence this.  Not all versions of git-rev-parse
# understand this option; we err on the side of trying the unshallow.
if [ "x$(git rev-parse --is-shallow-repository)" != xfalse ]; then
    # TODO really we want something like the converse of
    #  git fetch --shallow-exclude
    # but it doesn't seem to exist.
    git fetch --unshallow "$ORIGIN" "${refspecs[@]}"
fi

for tbranch in $BASE_BRANCHES; do
    trevlist="$(tlref)..HEAD"
    tcount=$(git rev-list --count "$trevlist")
    printf "HEAD is %3d commits ahead of %s\n" "$tcount" "$tbranch"
    if [ "$count" ] && [ $count -le $tcount ]; then continue; fi
    count=$tcount
    branch=$tbranch
    revlist=$trevlist
done

echo "Testing every commit not already on $branch"

commits=$(git rev-list --reverse "$revlist")

i=0
for commit in $commits; do
    banner='##############################'
    printf "|\n%s %d/%d %s\n|\n" "$banner" "$i" "$count" "$banner"
    git log -n1 "$commit" | sed 's/^/| /'
    echo '|'

    git_checkout_maybe_clean x "$commit"

    if x "$@"; then :; else
	rc="$?"
	printf "
|
========================= Failed after $i/$count =========================
|
Earliest broken commit is
  "
	git --no-pager log -n1 --pretty=oneline "$commit"
	exit "$rc"
    fi
    i=$(( $i + 1 ))
done

echo "|
$banner ok $banner
|"

rc=0