#!/bin/sh
# Copyright (c) 2015-2017, Richard Gallamore
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.

PORTEST_VERSION="0.1.9"

usage() {
	cat << EOF
Usage: portest [-m git] [-f bulkfile] [-U default] [-d PORTSDIR] \\
		[-z "set1 set2"] [-j "jail1 jail2"] [-abcCdFprRtTuv] \\
		file.diff cat/port ...

Parameters:
    -a     --  The equivalent to (-ptf /tmp/build.ports.txt).
    -c     --  Create repo, see -m for supported protocols, default is svn.
    -f     --  Generate poudiere usable bulk file.
    -p     --  Patch, portsnap will create a backup directory which is used
               on soft reverts (-r).
    -r     --  "Soft" revert, a quick and effective way to revert.
               portsnap uses backup created when patch (-p) is used, fast
               git (-r) is same as (-R) see below
               svn reverts all files from input patchfiles, fast
               multiple [-Rr] will revert twice, before and after a patch
    -R     --  "Hard" revert, will reset the repo.
               portsnap will rebuild tree, very slow
               git does hard reset and cleans all files, fast
               svn removed all files via status and revert -R, very slow!!
               multiple [-Rr] will revert twice, before and after a patch
    -t     --  Test with portlint.
    -T     --  Test with port test, or poudriere depending on USE_POUDRIERE.
    -u     --  Update repo, if revert -r or -R is used, revert will occur
               before update. This is Suggested with git and portsnap.
    -v     --  Show version of portest then exit 0.
Options:
    -b     --  Generate the list of cat/ports based on port tree
               instead of diff, git and svn only, slow.
    -C     --  Check only, will not touch port tree, only vaild with -p
               git may display errors if files do not exist,
               these files are created without this option.
    -d     --  Sets PORTSDIR, -U overrides this option.
    -F     --  Force portest to continue on checks that aren't critical.
    -j     --  Jails to use for poudriere, if multiple are used must be
               in "" with a space as a seperator. If no jails are selected
               all jails will be used, only valid with -T option.
    -m     --  Repo method, only used with -c valid methods are
               git,git+https,git+ssh,svn,svn+http,svn+https,
               svn+ssh,portsnap. Default method is svn.
    -U     --  Sets USE_POUDRIERE="YES" and sets port tree to use.
    -z     --  This specifies which SET to use for poudriere.
               Multiple sets can be used, 0 is the default set.
EOF
	exit 1
}

ALL="0"
POUDRIERE_BULK="0"
DO_PATCH="0"
REVERT_REPO="0"
RESET_REPO="0"
TEST_PORTLINT="0"
TEST_BUILD="0"
REPO_BUILD_LIST="0"
CREATE_REPO="0"
UPDATE_REPO="0"
CHECK_ONLY="0"
POUDRIERE_SETS="0"
SKIP_FAILED="0"

while getopts ":abcCd:j:f:Fm:prRtTuU:vz:" FLAG; do
	case "${FLAG}" in
	a)	ALL="1" ;;
	b)	REPO_BUILD_LIST="1" ;;
	c)	CREATE_REPO="1" ;;
	C)	CHECK_ONLY="1" ;;
	d)	PORTSDIR="${OPTARG}" ;;
	j)	POUDRIERE_JAILS="${OPTARG}" ;;
	f)	POUDRIERE_BULK="1"
		BULK_FILE="${OPTARG}" ;;
	F)	SKIP_FAILED="1" ;;
	m)	METHOD="${OPTARG}" ;;
	p)	DO_PATCH="1" ;;
	r)	REVERT_REPO="$(( ${REVERT_REPO} + 1 ))" ;;
	R)	REVERT_REPO="$(( ${REVERT_REPO} + 1 ))"
		RESET_REPO="1" ;;
	t)	TEST_PORTLINT="1" ;;
	T)	TEST_BUILD="1" ;;
	u)	UPDATE_REPO="1" ;;
	U)	USE_POUDRIERE="YES"
		POUDRIERE_PORT="${OPTARG}" ;;
	v)	echo "${PORTEST_VERSION}" && exit 0 ;;
	z)	POUDRIERE_SETS="${OPTARG}" ;;
	*)	usage && exit 1 ;;
	esac
done

[ $# -lt 1 ] && usage

shift "$((OPTIND-1))"

if [ ${REPO_BUILD_LIST} -ne 1 ]; then
[ $(( ALL + POUDRIERE_BULK + DO_PATCH + TEST_PORTLINT + TEST_BUILD )) -ge 1 ] && [ $# -lt 1 ] && usage
elif [ $(( DO_PATCH )) -ge 1 ] && [ $# -lt 1 ]; then
	usage;
fi

: ${METHOD:="svn"}
if [ "${CREATE_REPO}" -eq "1" ] && [ "${USE_POUDRIERE}" != "YES" ]; then
	: ${GIT_URL:="github.com/freebsd/freebsd-ports"}
	: ${SVN_URL:="svn.FreeBSD.org/ports"}
	case ${METHOD} in
	git) PROTO="git" ;;
	git+https) PROTO="https" ;;
	git+ssh) PROTO="git" ;;
	svn) PROTO="svn" ;;
	svn+http) PROTO="http" ;;
	svn+https) PROTO="https" ;;
	svn+ssh) PROTO="svn+ssh" ;;
	portsnap) ;;
	*) usage;;
	esac

	case ${METHOD} in
	git*) : ${BRANCH:="master"} ;;
	svn*) : ${BRANCH:="head"} ;;
	esac

	case ${METHOD} in
	git+ssh) GIT_REPO_URL="`echo ${PROTO}@${GIT_URL} | sed 's/\//\:&/'`" ;;
	git*) GIT_REPO_URL="${PROTO}://${GIT_URL}" ;;
	svn*) SVN_REPO_URL="${PROTO}://${SVN_URL}/${BRANCH}" ;;
	esac
fi

: ${USE_POUDRIERE:="NO"}
if [ "${USE_POUDRIERE}" == "YES" ]; then
	: ${POUDRIERE_ETC:="/usr/local/etc"}
	if [ -f "${POUDRIERE_ETC}/poudriere.conf" ]; then
	. ${POUDRIERE_ETC}/poudriere.conf
	elif [ -f "${POUDRIERE_ETC}/poudriere.d/poudriere.conf" ]; then
	. ${POUDRIERE_ETC}/poudriere.d/poudriere.conf
	fi
fi

PATCH="patch"
PATCH_ARGS="-Ef"
PORTSNAP="portsnap"
PORTSNAP_FETCH_ARGS="fetch"
PORTSNAP_EXTRACT_ARGS="extract"
PORTSNAP_UPDATE_ARGS="update"
SVN="svn"
SVN_CHECKOUT_ARGS="checkout ${SVN_REPO_URL}"
SVN_PATCH_ARGS="patch"
SVN_REVERT_ARGS="revert -R"
SVN_UPDATE_ARGS="update"
GIT="git"
GIT_ADD_ARGS="add -AN"
GIT_CLEAN_ARGS="clean -fd -f -x -e /distfiles -e /local -e /packages -e /INDEX-[0-9]* -e *~ -e *.sw[p-z]"
GIT_CLONE_ARGS="clone --depth 1 -b ${BRANCH} ${GIT_REPO_URL}"
GIT_APPLY_ARGS="apply --whitespace=nowarn"
GIT_RESET_ARGS="reset --hard"
GIT_UPDATE_ARGS="pull"
OPENSSL="openssl"
OPENSSL_ARGS="rand -hex 4"
PORTLINT="portlint"

: ${BULK_FILE:="/tmp/build.ports.txt"}
: ${PORTSDIR:="/usr/ports"}
: ${PORTLINT_ARGS:="-AC"}

if [ "${USE_POUDRIERE}" == "YES" ]; then
	POUDRIERE="poudriere"
	: ${POUDRIERE_JAILS:=""}
	: ${BASEFS:="/usr/local/poudriere"}
	: ${POUDRIERE_PORTSDIR:="${BASEFS}/ports"}
	: ${POUDRIERE_DATA=:"${BASEFS}/data"}
	: ${POUDRIERE_PORT:="default"}
	POUDRIERE_JAIL_ARGS="jail -i"
	POUDRIERE_PORTS_ARGS="ports -c -m ${METHOD} -p ${POUDRIERE_PORT}"
	poudriere_args() {
		if [ "$1" == "0" ]; then
			local POUDRIERE_SET=""
		else
			local POUDRIERE_SET="-z $1"
		fi
	POUDRIERE_BULK_ARGS="bulk ${POUDRIERE_SET} -t -C -p ${POUDRIERE_PORT}"
	POUDRIERE_STATUS_ARGS="status ${POUDRIERE_SET:="-z 0"} -cH"
	}
	PORTSDIR="${POUDRIERE_PORTSDIR}/${POUDRIERE_PORT}"
	TMUX_COMMAND="tmux"
	: ${NICE:="+15"}
	: ${TMUX_LAYOUT:="tiled"}
elif [ "${TEST_BUILD}" -eq 1 ]; then
	PORT="port"
	PORT_ARGS="test"
	MAKE="make"
	: ${MAKE_ARGS:="depends BATCH=yes"}
fi
CURDIR="`pwd`"
# Check commands #
check_exit() {
if [ ${SKIP_FAILED} -eq "0" ]; then
	echo "$1" && exit 1
else
	echo "$1 $2 (-F)"
fi
}
check_command() {
if ! [ -x "`command -v $1`" ]; then
	echo "$1 not found. $2" && exit 1
fi
}
check_svn() {
if ! [ -x "`command -v ${SVN}`" ]; then
	SVN="svnlite"
	if ! [ -x "`command -v ${SVN}`" ]; then
		echo "svn or svnlite not found. Try installing it, pkg install subversion" && exit 1
	fi
fi
}
check_empty_dir() {
	mkdir -p "$1"
	[ ! "`find "$1" -type d -empty -maxdepth 1`" ] && echo "$1 is not empty" && exit 1
}
### Create Repo ###
if [ "${CREATE_REPO}" -eq "1" ]; then
	if [ "${USE_POUDRIERE}" == "YES" ]; then
		check_command "${POUDRIERE}" "Try installing it, pkg install poudriere"
		${POUDRIERE} ${POUDRIERE_PORTS_ARGS}
	else
	check_empty_dir "${PORTSDIR}"
		case ${METHOD} in
			git*)
			check_command "${GIT}" "Try installing it, pkg install git"
			${GIT} ${GIT_CLONE_ARGS} "${PORTSDIR}"
			if [ "$?" -ne "0" ]; then
			echo "git clone failed" && exit 1
			fi
			;;
			svn*)
			check_svn
			${SVN} ${SVN_CHECKOUT_ARGS} "${PORTSDIR}"
			if [ "$?" -ne "0" ]; then
			echo "svn checkout failed" && exit 1
			fi
			;;
			portsnap*)
			check_command "${PORTSNAP}"
			cd ${PORTSDIR} && PORTSDIR="`pwd`"
			if [ "${PORTSDIR}" != "/usr/ports" ]; then
				mkdir : ${PORTSNAP_WRKDIR:="${PORTSDIR}/.snap"}
				${PORTSNAP} -d "${PORTSNAP_WRKDIR}" -p "${PORTSDIR}" ${PORTSNAP_FETCH_ARGS} ${PORTSNAP_EXTRACT_ARGS}
			else
				${PORTSNAP} ${PORTSNAP_FETCH_ARGS} ${PORTSNAP_EXTRACT_ARGS}
			fi
			if [ "$?" -ne "0" ]; then
			echo "portsnap creation failed" && exit 1
			fi
			;;
		esac
	fi
fi
# Verify PORTSDIR #
! [ -d "${PORTSDIR}" ] && echo "PORTSDIR not set or incorrect directory" && exit 1
cd ${PORTSDIR} && PORTSDIR="`pwd`"
: ${BACKUPDIR:="${PORTSDIR}/.bak"}
[ "`head -1 "${PORTSDIR}/README" | grep -com1 \"FreeBSD Ports Collection\"`" -ne "1" ] && echo "PORTSDIR is set to an incorrect directory" && exit 1
# Check PORTSDIR for svn or git tree #
if [ -d "${PORTSDIR}/.svn" ]; then
	check_svn && REPO_TYPE="svn" REPO="${SVN}" REPO_STATUS="status"
	[ "${REVERT_REPO}" -ge "1" ] && [ "${RESET_REPO}" -ne "1" ] && [ $# -lt 1 ] && usage
fi
[ -d "${PORTSDIR}/.git" ] && check_command "${GIT}" "Try installing it, pkg install git" && REPO_TYPE="git" REPO="${GIT}" REPO_STATUS="status -s"
# If not, use portsnap #
if [ -z "${REPO_TYPE}" ]; then
	check_command "${PORTSNAP}"
	if [ "${PORTSDIR}" != "/usr/ports" ]; then
		: ${PORTSNAP_WRKDIR:="${PORTSDIR}/.snap"}
		: ${PORTSNAP_ARGS:="-d ${PORTSNAP_WRKDIR} -p ${PORTSDIR}"}
	! [ -d "${PORTSNAP_WRKDIR}" ] && echo "Unsupported repo or portsnap wrkdir not found. Set PORTSNAP_WRKDIR if the latter" && exit 1
	else
		: ${PORTSNAP_WRKDIR:=""}
		: ${PORTSNAP_ARGS:=""}
	fi
	REPO_TYPE="portsnap"
fi
if [ "${CHECK_ONLY}" -eq "1" ]; then
	case ${REPO_TYPE} in
		svn*) SVN_CHECK_ARGS="--dry-run" ;;
		git*) GIT_CHECK_ARGS="--check" ;;
		portsnap*) PATCH_CHECK_ARGS="-C" ;;
	esac
fi
if [ "${REPO_BUILD_LIST}" -eq "1" ]; then
	case ${REPO_TYPE} in
	svn*);;
	git*);;
	*) echo "Port tree is not a svn or git repo, This is required with the -b option" && exit 1 ;;
	esac
fi
# Sets absolute path for patchfile or checks for proper cat/port #
cd "${PORTSDIR}"
for arg in "${@}"; do
	if [ -f "$arg" ]; then
		PATCH_FILE="${PATCH_FILE}
$arg"
	elif [ -f "${CURDIR}/$arg" ]; then
		PATCH_FILE="${PATCH_FILE}
${CURDIR}/$arg"
	elif [ -d "${PORTSDIR}/$arg" ]; then
		CAT_PORT="${CAT_PORT}
$arg/"
	else
		check_exit "$arg not found" "ignoring"
	fi
done

for file in ${PATCH_FILE}; do
	if [ "`cat "$file" | grep -com1 '^+++[ ].*'`" -eq "1" ]; then
		UNI_PATCH_FILE="${UNI_PATCH_FILE}
$file"
	else
		check_exit "$file is not a recognized patch format" "ignoring"
	fi
done

# Check for valid jails #
if [ "${USE_POUDRIERE}" == "YES" ] && [ "${TEST_BUILD}" -eq "1" ]; then

	check_command "${POUDRIERE}" "Try installing it, pkg install poudriere"

	# if no jails were set with -j use all jails for testing
	if [ -z "${POUDRIERE_JAILS}" ]; then
		echo "no jails selected; using all existing jails"
		POUDRIERE_JAILS="`poudriere jail -lnq`"
	else
		POUDRIERE_JAILS=`printf "%s\n" ${POUDRIERE_JAILS} | sort -u`
		for jail in ${POUDRIERE_JAILS}; do
			${POUDRIERE} ${POUDRIERE_JAIL_ARGS} -j $jail >/dev/null 2>&1
			[ $? -ne 0 ] && echo "No such jail $jail" && exit 1
		done
	fi
fi

add_patchfile() {
	case ${REPO_TYPE} in
	svn*)	PATCH_FILES_LIST="${PATCH_FILES_LIST}
"`echo "$3" | grep '^[^\>][ ][ ][ ].*' |  awk '{ print $(NF) }'` ;;
	git*)	PATCH_FILES_LIST="${PATCH_FILES_LIST}
"`echo "$3" | grep '\.\.\.$' | awk '{ print $(NF) }'| sed 's/\.\.\.$//'` ;;
	portsnap*)	PATCH_FILES_LIST="${PATCH_FILES_LIST}
"`echo "$3" | grep -A 2 '\-\-\-\-' | grep '\.\.\.$' | awk '{ print $3 }' | grep -v '^to$'` ;;
	esac
	PATCH_STRIP_COUNT="${PATCH_STRIP_COUNT}
$2"
}

patchfile_check() {
local HIGHEST_SUCCESS="0"
local HIGHEST_FAILED="0"
local STRIP_COUNT="$3"
local FILE="`cat "$1" | grep -v '/dev/null' | grep -Em1 '^(\+\+\+|---)[ ].*' | awk '{ print $2 }'`"
cd ${PORTSDIR}
while [ "$2" -ge "${STRIP_COUNT}" ]; do
	if [ "${REPO_TYPE}" == "svn" ] && [ "`head -5 "$1" | grep -com1 '\-\-git'`" -eq "1" ]; then
		local check_file="`echo "${FILE}" | sed -E "s/^([^\/]*\/){$(( ${STRIP_COUNT} + 1 ))}// ; s/\/.*//"`"
	else
		local check_file="`echo "${FILE}" | sed -E "s/^([^\/]*\/){${STRIP_COUNT}}// ; s/\/.*//"`"
	fi
	if [ -e "${PORTSDIR}/${check_file}" ] || [ "../" == "${check_file}" ]; then
		case ${REPO_TYPE} in
		svn*)	local OUTPUT="`${SVN} ${SVN_PATCH_ARGS} --dry-run --strip ${STRIP_COUNT} "$1" "${PORTSDIR}"`"
			local SUCCESS="`echo "${OUTPUT}" | grep -co '^.[ ][ ][ ]*'`"
			local FAILED="`echo "${OUTPUT}" | grep -co '^Skipped'`"
			[ "${STRIP_COUNT}" -eq "0" ] && [ "${FAILED}" -eq "0" ] && add_patchfile "$1" "${STRIP_COUNT}" "${OUTPUT}" && return ;;
		git*)	local OUTPUT="`${GIT} ${GIT_APPLY_ARGS} -p${STRIP_COUNT} --check -v "$1" 2>&1`"
			[ "$?" -eq "0" ] && add_patchfile "$1" "${STRIP_COUNT}" "${OUTPUT}" && return
			[ "`echo "${OUTPUT}" | grep -com1 'patch does not apply'`" -ge "1" ] && add_patchfile "$1" "${STRIP_COUNT}" "${OUTPUT}" && return
			local SUCCESS="`echo "${OUTPUT}" | grep -co '\.\.\.$'`"
			local FAILED="`echo "${OUTPUT}" | grep -co ':'`"
			local SUCCESS="$(( ${SUCCESS} - ${FAILED} ))" ;;
		portsnap*)	local OUTPUT="`${PATCH} ${PATCH_ARGS} -C -p${STRIP_COUNT} -d "${PORTSDIR}" -i "$1"`"
				local SUCCESS="`echo "${OUTPUT}" | grep -co 'succeeded'`"
				local FAILED="`echo "${OUTPUT}" | grep -Eco '[0-9]+ ignored'`" ;;
		esac
		[ "$(( ${HIGHEST_SUCCESS} - ${HIGHEST_FAILED} ))" -lt "$(( ${SUCCESS} - ${FAILED} ))" ] && local HIGHEST_SUCCESS="${SUCCESS}" local HIGHEST_FAILED="${FAILED}" local HIGHEST_STRIP_COUNT="${STRIP_COUNT}" local HIGHEST_OUTPUT="${OUTPUT}"
	fi
	local STRIP_COUNT=$(( ${STRIP_COUNT} + 1 ))
	if [ "$2" -le "${STRIP_COUNT}" ]; then
		if [ "${HIGHEST_SUCCESS}" -gt "${HIGHEST_FAILED}" ] && [ "${HIGHEST_SUCCESS}" -ne "0" ]; then
			add_patchfile "$1" "${HIGHEST_STRIP_COUNT}" "${HIGHEST_OUTPUT}" && return
		else
			check_exit "Patchfile "$1" Failed, try a revert?" "ignoring"
		fi
	fi
done
}

patchfile_patch() {
local DOLLARSIGN="$"
local POS="1"
for file in $1; do
	local pos=`echo $2 | awk "{ print ${DOLLARSIGN}${POS} }"`
	case ${REPO_TYPE} in
		svn*)	PATCH_OUTPUT="${PATCH_OUTPUT}
"`${SVN} ${SVN_PATCH_ARGS} ${SVN_CHECK_ARGS} --strip $pos "$file" "${PORTSDIR}" 2>&1 | grep '^. '` ;;
		git*)	PATCH_OUTPUT="${PATCH_OUTPUT}
"`${GIT} ${GIT_APPLY_ARGS} ${GIT_CHECK_ARGS} -p$pos "$file" 2>&1` ;;
		portsnap*)	PATCH_OUTPUT="${PATCH_OUTPUT}
"`${PATCH} ${PATCH_ARGS} ${PATCH_CHECK_ARGS} -s -p$pos -d "${PORTSDIR}" -i "$file" 2>&1` ;;
	esac
	local POS="$(( ${POS} + 1 ))"
done
}

filelist() {
[ "${REPO_TYPE}" == "portsnap" ] && check_command "${PATCH}"
for patchfile in ${UNI_PATCH_FILE}; do
	local MAX_STRIP_COUNT="`cat "$patchfile" | grep -v '/dev/null' | grep -Em1 '^(\+\+\+|---)[ ].*' | awk -F/ '{ print NF-1 }'`"
	local STRIP_COUNT="`cat "$patchfile" | grep -v '/dev/null' | grep -Em2 '^(\+\+\+|---)[ ].*' | awk '{ print $2 }' | grep -com1 '^/'`"
	patchfile_check "$patchfile" "${MAX_STRIP_COUNT}" "${STRIP_COUNT}"
done
PATCH_FILES_LIST="`echo "${PATCH_FILES_LIST}
${CAT_PORT}" | sort -u`"
}

folderlist() {
[ -z "${PATCH_FILES_LIST}" ] && filelist
[ -n "${PATCH_FILES_LIST}" ] && PATCH_FOLDER_LIST="`echo "${PATCH_FILES_LIST}" | grep -Eo '^([^/]+\/+[^/]+\/+[^/]+\/|[^/]+\/+[^/]+\/)' | sed 's/\/$//' | sort -u`"
}

# Generate a list of ports for poudriere to build #
buildlist() {
if [ "${REPO_BUILD_LIST}" -eq "1" ]; then
cd ${PORTSDIR}
BUILD_LIST="`${REPO} ${REPO_STATUS} | awk '{ print $(NF) }' | grep -Eo '^[^A-Z][^/]+\/+[^/]+\/' | sed 's/\/$//' | grep "/" | sort -u`"
[ -z "${CAT_PORT}" ] && BUILD_LIST="`echo "${BUILD_LIST}
${CAT_PORT}" | grep '.*' | sort -u`"
else
[ -z "${PATCH_FILES_LIST}" ] && filelist
BUILD_LIST="`echo "${PATCH_FILES_LIST}" | grep -Eo '^[^A-Z][^/]+\/+[^/]+\/' | sed 's/\/$//' | grep "/" | sort -u`"
fi
}

git_revert() {
if [ "${REVERT_REPO}" -ge "1" ]; then
${GIT} ${GIT_RESET_ARGS}
${GIT} ${GIT_CLEAN_ARGS}
REVERT_REPO="$(( ${REVERT_REPO} - 1 ))"
fi
}

portsnap_revert() {
if [ "${REVERT_REPO}" -ge "1" ] && [ "${RESET_REPO}" -ne "1" ]; then
	! [ -d "${BACKUPDIR}" ] && check_exit "Nothing to revert, try the -R option?" "ignoring" && return
	cd ${BACKUPDIR}
	for dir in `find ./ -type d -maxdepth 1 | sed 's/^\.\///' | grep '^[A-Z]'`; do
		rm -rf "${PORTSDIR}/$dir"
		mv "${BACKUPDIR}/$dir" "${PORTSDIR}/$dir"
	done
	rmdir -p `find ./ -type d -mindepth 2 -maxdepth 3 -empty | sed 's/^\.\///'` >/dev/null 2>&1
	for dir in `find ./ -type d -mindepth 2 -maxdepth 2 | sed 's/^\.\///'`; do
		rm -rf "${PORTSDIR}/$dir"
		[ -d "${BACKUPDIR}/$dir" ] && mv "${BACKUPDIR}/$dir" "${PORTSDIR}/$dir"
	done
	for file in `find ./ -type f -maxdepth 2 | sed 's/^\.\///'`; do
		rm -f "${PORTSDIR}/$file"
		mv "${BACKUPDIR}/$file" "${PORTSDIR}/$file"
	done
	cd ${PORTSDIR} && rm -rf "${BACKUPDIR}"
	rm `find ./ -maxdepth 2 -name "*.[ro][er][ji]*" | grep -v distfiles` >/dev/null 2>&1
elif [ "${REVERT_REPO}" -ge "1" ] && [ "${RESET_REPO}"  -eq "1" ]; then
	${PORTSNAP} ${PORTSNAP_ARGS} ${PORTSNAP_EXTRACT_ARGS}
	cd ${PORTSDIR} && rm -rf "${BACKUPDIR}"
	rm `find ./ -maxdepth 1 -name "*.[ro][er][ji]*"` >/dev/null 2>&1
	REVERT_REPO="$(( ${REVERT_REPO} - 1 ))"
	RESET_REPO="0"
fi
}

svn_revert() {
if [ "${REVERT_REPO}" -ge "1" ] && [ "${RESET_REPO}" -eq "1" ]; then
	for file in `${SVN} status | awk '{ print $(NF) }'`; do
		rm -rf "${PORTSDIR}/$file"
	done
	${SVN} ${SVN_REVERT_ARGS} ${PORTSDIR}
	rm `find ./ -maxdepth 1 -name "*.[ro][er][ji]*"` >/dev/null 2>&1
	REVERT_REPO="$(( ${REVERT_REPO} - 1 ))"
	RESET_REPO="0"
elif [ "${REVERT_REPO}" -ge "1" ] && [ "${RESET_REPO}" -ne "1" ]; then
	[ -z "${BUILD_LIST}" ] && buildlist
# Wierd cases where svn won't see short filenames in list.    #
# This seems to be an svn bug, this list helps in those cases #
	PATCH_FILES_FILTERED="`echo "${PATCH_FILES_LIST}" | grep -v '^[^/]*\/[^/]*\/[^/]*'`"
	for file in ${BUILD_LIST}; do
		rm -rf "${PORTSDIR}/$file"
	done
	for file in ${PATCH_FILES_LIST}; do
		rm -rf "${PORTSDIR}/$file"
	done
	[ -n "${BUILD_LIST}" ] && ${SVN} ${SVN_REVERT_ARGS} ${BUILD_LIST}
	[ -n "${PATCH_FILES_LIST}" ] && ${SVN} ${SVN_REVERT_ARGS} ${PATCH_FILES_LIST}
	[ -n "${PATCH_FILES_FILTERED}" ] && ${SVN} ${SVN_REVERT_ARGS} ${PATCH_FILES_FILTERED}
	rm `find ./ -maxdepth 1 -name "*.[ro][er][ji]*"` >/dev/null 2>&1
	REVERT_REPO="$(( ${REVERT_REPO} - 1 ))"
fi
}
if [ "${REVERT_REPO}" -gt "1" ] || [ "${UPDATE_REPO}" -eq "1" ] && [ "${REVERT_REPO}" -ge "1" ]; then
	case ${REPO_TYPE} in
		svn) svn_revert ;;
		git) git_revert ;;
		portsnap) portsnap_revert ;;
	esac
	[ "${DO_PATCH}" -ne "1" ] && REVERT_REPO=0
fi

### Update Repo ###
if [ "${UPDATE_REPO}" -eq "1" ]; then
cd ${PORTSDIR}
	case ${REPO_TYPE} in
		svn)		${SVN} ${SVN_UPDATE_ARGS} ;;
		git)		${GIT} ${GIT_UPDATE_ARGS} ;;
		portsnap)	${PORTSNAP} ${PORTSNAP_ARGS} ${PORTSNAP_FETCH_ARGS} ${PORTSNAP_UPDATE_ARGS} ;;
	esac
fi

### Patch Tree ###
if [ -n "${UNI_PATCH_FILE}" ] && [ "${DO_PATCH}" -eq "1" ] || [ "${ALL}" -eq "1" ]; then
	if [ "${REPO_TYPE}" == "svn" ] || [ "${CHECK_ONLY}" -eq "1" ] ; then
		[ -z "${PATCH_FILES_LIST}" ] && filelist
	else
		[ -z "${PATCH_FOLDER_LIST}" ] && folderlist
	fi
	if [ "${CHECK_ONLY}" -ne "1" ]; then
		case ${REPO_TYPE} in
		portsnap*)	mkdir -p ${BACKUPDIR} && cd ${BACKUPDIR}
				mkdir -p `echo "${PATCH_FILES_LIST}" | sed 's/^[A-Z].*//' | grep -o '^[^/]*\/[^/]*\/' | sed 's/\/$// ; s/[^/]*$//' | sort -u`
				cd ${PORTSDIR}
				for dir in `find ./ -type d -maxdepth 1 | sed 's/^\.\///' | grep '^[A-Z]'`; do
					! [ -d "${BACKUPDIR}/$dir" ] && cp -Ra "${PORTSDIR}/$dir" "${BACKUPDIR}/$dir"
				done
				for file in `find ./ -type f -maxdepth 2 | sed 's/^\.\/// ; s/distfiles.*//'`; do
					! [ -f "${BACKUPDIR}/$file" ] && cp -a "${PORTSDIR}/$file" "${BACKUPDIR}/$file" >/dev/null 2>&1
				done
				for dir in `echo "${PATCH_FOLDER_LIST}"`; do
					! [ -d "${PORTSDIR}/$dir" ] && mkdir -p "${PORTSDIR}/$dir"
				done
				for dir in `echo "${PATCH_FILES_LIST}" | grep -o '^[^/]*\/[^/]*\/' | sed 's/\/$//' | sort -u`; do
					! [ -d "${BACKUPDIR}/$dir" ] && cp -Ra "${PORTSDIR}/$dir" "${BACKUPDIR}/$dir"
				done ;;
		esac
	fi
	cd ${PORTSDIR}
	patchfile_patch "${UNI_PATCH_FILE}" "${PATCH_STRIP_COUNT}"
	case ${REPO_TYPE} in
	svn*)	if [ "`echo "${PATCH_OUTPUT}" | grep -com1 reject`" -gt "0" ]; then
			echo "${PATCH_OUTPUT}" | grep '^[^ ].*'
			check_exit "Patch Failed" "continuing"
		fi
		echo "${PATCH_OUTPUT}" | grep '^[^ ].*';;
	git*)	if [ "`echo "${PATCH_OUTPUT}" | grep -com1 error`" -gt "0" ]; then
			echo "${PATCH_OUTPUT}" | grep '^[^ ].*'
			check_exit "Patch Failed" "continuing"
		fi
		echo "${PATCH_OUTPUT}" | grep '^[^ ].*'
		${GIT} ${GIT_ADD_ARGS} ${PATCH_FOLDER_LIST}
		[ "${CHECK_ONLY}" -ne "1" ] && rm `find ${PATCH_FOLDER_LIST} -maxdepth 1 -empty 2>/dev/null` 2>/dev/null ;;
	*)	if [ "`echo "${PATCH_OUTPUT}" | grep -com1 rejects`" -gt "0" ]; then
			echo "${PATCH_OUTPUT}" | grep '^[^ ].*'
			check_exit "Patch Failed" "continuing"
		fi
		echo "${PATCH_OUTPUT}" | grep '^[^ ].*'
		[ "${CHECK_ONLY}" -ne "1" ] && cd ${PORTSDIR} && rm `find ${PATCH_FOLDER_LIST} -maxdepth 1 -name "*.orig"` 2>/dev/null ;;
	esac
fi

### Bulk File ###
if [ "${POUDRIERE_BULK}" -eq "1" ] || [ "${TEST_BUILD}" -eq "1" ] || [ "${ALL}" -eq "1" ]; then
	[ -z "${BUILD_LIST}" ] && buildlist
	cd "${CURDIR}" && echo "${BUILD_LIST}" >"${BULK_FILE}"
fi

### Portlint ###
if [ "${TEST_PORTLINT}" -eq "1" ] || [ "${ALL}" -eq "1" ]; then
	check_command "${PORTLINT}" "Try installing it, pkg install portlint"
	[ -z "${BUILD_LIST}" ] && buildlist
	for port in ${BUILD_LIST}; do
		echo "$port"
		${PORTLINT} ${PORTLINT_ARGS} "${PORTSDIR}/$port"
	done
fi


### Port test and Poudriere testing ###
if [ "${TEST_BUILD}" -eq "1" ] && [ "${USE_POUDRIERE}" != "YES" ]; then
	check_command "${PORT}" "Try installing it, pkg install porttools"
	check_command "${MAKE}"
	rm -rf "`find ${PORTSDIR} -type d -name "work" -maxdepth 3`"
	[ -z "${BUILD_LIST}" ] && buildlist
	for port in ${BUILD_LIST}; do
		cd "${PORTSDIR}/$port"
		${MAKE} ${MAKE_ARGS}
		${PORT} ${PORT_ARGS}
	done
elif [ "${TEST_BUILD}" -eq "1" ] && [ "${USE_POUDRIERE}" == "YES" ]; then
	check_command "${TMUX_COMMAND}" "Try installing it, pkg install tmux"
	[ -z ${TMUX_SESSION} ] && check_command "${OPENSSL}" "Try installing it, pkg install openssl"
	[ -z "${BUILD_LIST}" ] && buildlist
	: ${TMUX_SESSION:="portest-`${OPENSSL} ${OPENSSL_ARGS}`"}

	poudriere_init() {
	printf "Verifying jails not already running"
	for jail in ${POUDRIERE_JAILS}; do
		if [ "`${POUDRIERE} ${POUDRIERE_STATUS_ARGS} -j $jail | awk '{ print $2 }' | grep -com1 "^${POUDRIERE_PORT}$"`" -eq "1" ]; then
			echo " $jail is already running on ${POUDRIERE_PORT}" && exit 1
		fi
		printf "."
	done
	printf " Done\n"
	printf "Starting jails"
	cd "${CURDIR}"
	for jail in ${POUDRIERE_JAILS}; do
		if [ "`${TMUX_COMMAND} list-sessions 2>/dev/null | grep -com1 "${TMUX_SESSION}"`" -ne "1" ]; then
			${TMUX_COMMAND} new-session -ds "${TMUX_SESSION}" nice -n ${NICE} ${POUDRIERE} ${POUDRIERE_BULK_ARGS} -j $jail -f ${BULK_FILE}
		else
			${TMUX_COMMAND} split-window -dt "${TMUX_SESSION}" nice -n ${NICE} ${POUDRIERE} ${POUDRIERE_BULK_ARGS} -j $jail -f ${BULK_FILE}
		fi
		sleep 1
		printf "."
		${TMUX_COMMAND} select-layout -t "${TMUX_SESSION}" ${TMUX_LAYOUT}
	done
	printf " Done\n"
	printf "Verifying jails started successfully... Please wait"
	: ${JAIL_MAX_VERIFY:="3"}
	JAIL_VERIFY="0"
	JAILS_STARTED="0"
	while [ ${JAILS_STARTED} -ne "1" ] && [ "${JAIL_MAX_VERIFY}" -ge "${JAIL_VERIFY}" ]; do
		sleep 10
		JAILS_STARTED="1"
		for jail in ${POUDRIERE_JAILS}; do
			OUTPUT="`${POUDRIERE} ${POUDRIERE_STATUS_ARGS} -j $jail | awk '{ print $2 }' | grep -com1 "^${POUDRIERE_PORT}$"`"
			if [ "${OUTPUT}" -ne "1" ]; then
				if [ "`${TMUX_COMMAND} list-sessions | grep -com1 "${TMUX_SESSION}"`" -eq "1" ]; then
					${TMUX_COMMAND} split-window -dt "${TMUX_SESSION}" nice -n ${NICE} ${POUDRIERE} ${POUDRIERE_BULK_ARGS} -j $jail -f ${BULK_FILE}
				else
					${TMUX_COMMAND} new-session -ds "${TMUX_SESSION}" nice -n ${NICE} ${POUDRIERE} ${POUDRIERE_BULK_ARGS} -j $jail -f ${BULK_FILE}
				fi
			fi
			if [ "${JAILS_STARTED}" -ne "0" ] && [ "${OUTPUT}" -eq "1" ] ; then
				JAILS_STARTED="1"
			else
				JAILS_STARTED="0"
			fi
			printf "."
			${TMUX_COMMAND} select-layout -t "${TMUX_SESSION}" ${TMUX_LAYOUT}
		done
		JAIL_VERIFY=$(( ${JAIL_VERIFY} + 1 ))
	done
	printf " done\n"
	}
	POUDRIERE_SETS=`printf "%s\n" ${POUDRIERE_SETS} | sort -u`
		for sets in ${POUDRIERE_SETS}; do
			poudriere_args "$sets"
			poudriere_init
		done
	${TMUX_COMMAND} attach-session -t "${TMUX_SESSION}" >/dev/null 2>&1
	if [ "$?" -ne 0 ]; then
		${TMUX_COMMAND} switch -t "${TMUX_SESSION}"
	fi
	if [ "${REVERT_REPO}" -ge "1" ]; then
		while [ "`${TMUX_COMMAND} list-sessions -F \#S | grep -com1 "${TMUX_SESSION}"`" -eq "1" ]; do
			sleep 10
		done
	fi
fi

### Revert Patch ###
cd ${PORTSDIR}
case ${REPO_TYPE} in
	svn*)		svn_revert ;;
	git*)		git_revert ;;
	portsnap*)	portsnap_revert ;;
esac
