#!/bin/sh
unset CDPATH	# See https://bosker.wordpress.com/2012/02/12/bash-scripters-beware-of-the-cdpath/

export LANG=C

SPLIT_SEPARATOR=-

repotoolopt=""
debug=no
compareopt=""
svnquiet=-q
slimit=""
rlimit=""
progress=no
readopt=""
while getopts ade:hir:R:suvpq opt
do
    case $opt in
	d) debug=yes;;
	p) progress=yes;;
	e) compareopt="${compareopt} -e ${OPTARG}";;
	a) svnquiet="";;
	i|s|u) compareopt="${compareopt} -${opt}";;
	R) slimit="-r ${OPTARG}" rlimit="readlimit ${OPTARG}";;
	r) revision="${OPTARG}"; readopt="--preserve";; # --preserve is to avoid setting commit.Branch
	                                                # to something unrelated, because we rely on it
	                                                # to know which dir to checkout in SVN.
	v|q) repotoolopt="${repotoolopt} -${opt}";;
	h) cat <<EOF
liftcheck - compare reposurgeon lift of a Subversion repo with a checkout

Produces an ok/not-ok line suitable for feeding to a TAP consumer.
If the comparison produces a nonempty diff, it is output as a YAML
block following the "not ok" line.  If the environment variable
QUIET is set to 1, the diff is suppressed.

If the ## line of the stream file contains the string TODO, any
TAP line generated by test failure or success will have a TODO directive.

The -v option reveals executed subcommands.
The -q option suppresses warnings on success; it also makes repotool
          extra quiet and reports only errors.
The -p option shows revision numbers when comparing multiple revisions
The -u option enables context-diffing in the compare operation
The -i option disables the normal suppression of ignore comparisons
The -d option disables removal of the generated repositories after the run
The -a option enables progress messages from the Subversion loader.
The -s option enables display of common files
The -r option sets a revision to check rather than trunk/branches/tags of HEAD.
          The corresponding git commit is found with the legacy map, which
          means only the branch modified in the revision will be compared,
          unless the -B option is also used to make a flat repository.
          You can use the "all" keyword or a range min:max to check multiple
          revisions in a row. Non-existing revisions are silently ignored,
The -R option sets a read limit to the dump parsing, for speed
The -e option sets exclude patterns to ignore uninteresting tags and branches
The -h option prints this usage summary.

The REPOSURGEON environment variable can be used to substitute in a
different implementation.

The ENGINE variable can be used to run a different converter. This is tricky,
read the code before trying to use it.  Presently the only engine supported
other than reposurgeon itself is svn2git, and that only wthout an -r option.

The TESTOPT variable can be used to pass an early command or option setting
to reposurgeon.
EOF
	exit 0
        ;;
	*) echo "$0: unknown flag $opt" >&2; exit 1;;
    esac
done
# shellcheck disable=SC2004
shift $(($OPTIND - 1))

if [ -z "$1" ]
then
    echo "not ok - liftcheck: an argument file (a Subversion dump) is required."
    exit 1
fi

ENGINE=${ENGINE:=reposurgeon}

rm -f /tmp/diff$$

# shellcheck disable=SC2068
for what in $@
do
    if [ -f "${what}".svn ]
    then
	what="${what}".svn
    elif [ ! -f "${what}" ]
    then
	echo "not ok - liftcheck: no Subversion dumpfile matching ${what} found"
	exit 1
    fi

    stem=liftcheck$$

    # shellcheck disable=SC2086
    rm -fr ${stem}*

    if [ $debug = no ]
    then
	# shellcheck disable=SC2064
	trap "rm -fr ${stem} ${stem}-checkout ${stem}-git ${stem}.gfi ${stem}.info ${stem}*~ ${stem}-tmpdir /tmp/diff$$" EXIT HUP INT QUIT TERM
    fi

    TMPDIR=$(readlink -f ${stem}-tmpdir)
    export TMPDIR
    mkdir "${TMPDIR}"

    description=$(sed -n <"${what}" -e '/ ##  */s///p' || echo 'no description')
    directive=""
    if grep ' ##.*TODO' "${what}" >/dev/null
    then
	directive='# TODO '
    elif grep ' ##.*SKIP' "${what}" >/dev/null
    then
	echo "ok - $ SKIP ${what}: ${description}"
	exit 0
    fi

    # Make a Subversion repo from the dump
    # shellcheck disable=SC2086
    ./svn-to-svn ${svnquiet} ${slimit} -c <${what} ${stem} \
    	|| { echo "not ok - ${directive}${what}: svn-to-svn within liftcheck failed"; exit 1; }


    cat /dev/null >/tmp/warnings$$
    case ${ENGINE} in
	 reposurgeon)
	     # Make a git repo from the dump using reposurgeon
	     ${REPOSURGEON:-../reposurgeon} "${TESTOPT}" "${rlimit}" "read ${readopt} <${what}" \
					    "prefer git" "rebuild ${stem}-git" "write >${stem}.gfi" >/tmp/warnings$$ 2>&1
	     ;;
	 svn2git)
	     # svn2git needs to be run in a scratch directory; it clobbers things
	     if [ ${progress} != no ]
	     then
		 outdev=stdout;
	     else
		 outdev=null
	     fi
	     svnrepo=file://${PWD}/${stem}
	     # shellcheck disable=SC2086,2164
	     (mkdir ${stem}-git; cd ${stem}-git >/dev/null; svn2git ${svnrepo} >/dev/${outdev};)
	     ;;
	 *)
	     echo "not ok - liftcheck: unknown conversion engine ${ENGINE}" >&2; exit 1
	     ;;
    esac

    if [ -e "${stem}-checkout/trunk" ] \
	|| [ -e "${stem}-checkout/branches" ] \
	|| [ -e "${stem}-checkout/tags" ]
    then
	structured=true
    else
	structured=
    fi

    if [ -n "${revision}" ]
    then
	if [ ! -f ${stem}-git/.git/marks ]
	then
	    echo Missing git marks file, unable to compare. >&2
	    exit 1
	fi

	# Generate a <legacy-id>TAB<mark>TAB<ref> file from the git-fast-import stream
	sed <"${stem}.gfi" -n '/^commit / h;
				  /^#legacy-id / H;
				  /^mark / {
					H; s/^.*$//; x;
					t next; :next;
					s/^commit \(.*\)\n#legacy-id \(.*\)\nmark \(:.*\)$/\2\t\3\t\1/;
					t ok; d;
					:ok; p
				    }' > "${stem}.info"
	rm -f "${stem}.gfi"

	if [ "${revision}" = "all" ]
	then
	    min=1
	    max=$(tail -n 1 ${stem}.info | cut -f 1 | cut -d$SPLIT_SEPARATOR -f 1)
	else
	    min=$(echo "${revision}" | sed 's/:.*$//')
	    max=$(echo "${revision}" | sed 's/^.*://')
	fi

	while read -r line # input file at the end of the loop
	do
	    # shellcheck disable=SC2086
	    revision=$(echo "${line}" | cut -f 1)
	    intrev=$(echo "${revision}" | cut -d$SPLIT_SEPARATOR -f 1)
	    if [ "${intrev}" -lt "${min}" ] || [ "${intrev}" -gt "${max}" ]
	    then
		continue
	    fi

	    # Find out the git revision to pass
	    mark=$(echo "${line}" | cut -f 2)
	    sha=$(grep "^${mark}" ${stem}-git/.git/marks --max-count=1 | cut -d" " -f 2)
	    if [ -z "${sha}" ]
	    then
		continue
	    fi

	    # Find the corresponding ref to checkout in SVN
	    ref=$(echo "${line}" | cut -f 3 | sed 's!refs/!!')

	    # Skip the comparison if the revision is "out of namespaces"
	    # because the Git side will only contain that outside part
	    # while the SVN side will contain the whole repository
	    if [ "${ref}" = "heads/unbranched" ]
	    then
		continue
	    fi

	    # Use -n (nobranch) if asked to, or if there is no structure and
	    # the asked branch is master
	    # shellcheck disable=SC2166
	    if [ "(" "${ref}" = "heads/master" ")" -a "(" -z "${structured}" ")" ]
	    then
		refopt=-n
	    else
		refopt=$(echo "${ref}" | sed 's!^deleted/r[^/]*/!!; s!^tags/!-t !; s!^heads/!-b !')
	    fi

	    if [ ${progress} != no ]
	    then
		echo "Checking lift at r${revision} (${refopt})"
	    fi
	    # Compare the original with the lift
	    # shellcheck disable=2086
	    ../${REPOTOOL:-repotool} compare ${repotoolopt} ${compareopt} \
		-a ${refopt} -r ${intrev}:${sha} \
		${stem}-checkout ${stem}-git >>/tmp/diff$$ 2>>/tmp/warnings$$ \
		|| { echo "not ok - ${directive}${what}: repotool within liftcheck failed"; exit 1; }
	done < "${stem}.info"
    else

	if [ -z "${structured}" ]
	then
	    # No structure at all, use -n (nobranch) to compare
	    # -a (accept-missing) is to use an empty dir if git has no master
	    # (this will only succeed if the SVN repository has no files)
	    compareopt="$compareopt -n -a"
	fi

	# Compare the original with the lift
	# shellcheck disable=SC2086
	../${REPOTOOL:-repotool} compare-all ${repotoolopt} ${compareopt} \
	   ${stem}-checkout ${stem}-git  tee /tmp/diff$$ 2>>/tmp/warnings$$ \
	    || { echo "not ok - ${directive}${what}: repotool within liftcheck failed"; exit 1; }
    fi

    if [ ! -s /tmp/diff$$ ]
    then
	echo "ok - ${directive}${what}: ${description}"
	if [ -s /tmp/warnings$$ ] && [ ! "${QUIET}" = 1 ] && [ -z "${repotoolopt}" ]
	then
	    echo "  --- |"
	    sed </tmp/warnings$$ -e 's/^/ /'
	    echo "  ..." 
	fi
	exit 0
    else
	echo "not ok - ${directive}${what}: ${description}"
	if [ "${directive}" = "" ] && [ ! "${QUIET}" = 1 ]
	then
	    echo "  --- |"
	    sed </tmp/warnings$$ -e 's/^/  /'
	    sed </tmp/diff$$ -e 's/^/  /'
	    echo "  ..." 
	fi
	[ "${directive}" = "" ] && exit 1 
    fi
    
    if [ $debug != no ]
    then
	ls -d liftcheck[0123456789]*
    fi
done
    
# end
