#!/bin/sh

# Copyright (c) 2012 - 2013 dak180 and contributors. See
# http://opensource.org/licenses/mit-license.php or the included
# COPYING.md for licence terms.
#
# autorevision - extracts metadata about the head version from your
# repository.

# Usage message.
arUsage() {
	cat > "/dev/stderr" << EOF
usage: ./autorevision {-t output-type | -s symbol} [-o cache-file [-f] ] [-V]
	Options include:
	-t output-type		= specify output type
	-s symbol		= specify symbol output
	-o cache-file		= specify cache file location
	-f			= force the use of cache data
	-V			= emit version and exit
	-?			= help message

The following are valid output types:
	h			= Header for use with c/c++
	xcode			= Header useful for populating info.plist files
	sh			= Bash sytax

The following are valid symbols:
	VCS_TYPE
	VCS_BASENAME
	VCS_BRANCH
	VCS_TAG
	VCS_EXTRA
	VCS_FULL_HASH
	VCS_SHORT_HASH
	VCS_WC_MODIFIED
	VCS_COMMIT_COUNT
	VCS_MOST_RECENT_TAGGED_VERSION
	VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION
	VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH
	VCS_BRANCH_COMMIT_COUNT
	VCS_MOST_RECENT_COMMIT_DATE
	VCS_MOST_RECENT_COMMIT_YEAR
EOF
	exit 1
}

# Config
ARVERSION="1.8-Warzone"
TARGETFILE="/dev/stdout"
while getopts ":t:o:s:Vf" OPTION; do
	case "${OPTION}" in
		t)
			AFILETYPE="${OPTARG}"
		;;
		o)
			CACHEFILE="${OPTARG}"
		;;
		f)
			CACHEFORCE="1"
		;;
		s)
			VAROUT="${OPTARG}"
		;;
		U)
			UNTRACKEDFILES="1"
		;;
		V)
			echo "autorevision ${ARVERSION}"
			exit 0
		;;
		?)
			# If an unknown flag is used (or -?):
			arUsage
		;;
	esac
done

if [ ! -z "${VAROUT}" ] && [ ! -z "${AFILETYPE}" ]; then
	# If both -s and -t are specified:
	echo "error: Improper argument combination." 1>&2
	exit 1
elif [ -z "${VAROUT}" ] && [ -z "${AFILETYPE}" ]; then
	# If neither -s or -t are specified:
	arUsage
elif [ -z "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
	# If -f is specified without -o:
	arUsage
fi

# Make sure that the path we are given is one we can source
# (dash, we are looking at you).
if ! echo "${CACHEFILE}" | grep -q '^\.*/'; then
	CACHEFILE="./${CACHEFILE}"
fi


# Functions to extract data from different repo types.
#
canExtractVersionNumberFromGitTag() {
	local version_tag="$1"

	# Remove "v/" or "v" prefix (as in "v3.2.2"), if present
	version_tag="$(echo "${version_tag}" | sed -e 's:^v/::' -e 's:^v::')"

	# Remove _beta* or _rc* suffix, if present
	version_tag="$(echo "${version_tag}" | sed -e 's:_beta.*$::' -e 's:_rc.*$::')"

	# Extract up to a 3-component version # from the beginning of the tag (i.e. 3.2.2)
	version_tag="$(echo "${version_tag}" | grep -Eo '^[[:digit:]]+(.[[:digit:]]+){0,2}')"

	if [ -n "${version_tag}" ]; then
		return 0
	else
		return 1 # failed to extract version #
	fi
}

# For git repos
gitRepo() {
	cd "$(git rev-parse --show-toplevel)"

	VCS_TYPE="git"
	VCS_BASENAME="$(basename "${PWD}")"

	# Check if working copy is clean, however, we will ignore any and all po files
	# when we determine if modifications were done.
	git update-index --assume-unchanged po/*.po
	test -z "$(git status --untracked-files=no --porcelain)"
	VCS_WC_MODIFIED="${?}"
	# now, reset index back to normal
	git update-index --no-assume-unchanged po/*.po

	# The full revision hash
	VCS_FULL_HASH="$(git rev-parse HEAD)"
	# The short hash
	VCS_SHORT_HASH="$(echo "${VCS_FULL_HASH}" | cut -b 1-7)"
	# Current branch (if we are on a branch...)
	VCS_BRANCH="$(git symbolic-ref --short -q HEAD)"
	# Check if we are on a tag
	VCS_TAG="$(git describe --exact-match 2> /dev/null)"
	# get some extra data in case we are not on a branch or a tag...
	VCS_EXTRA="$(git describe 2> /dev/null)"

	# get the # of commits in the current history
	# IMPORTANT: This value is incorrect if operating from a shallow clone
	VCS_COMMIT_COUNT="$(git rev-list --count HEAD 2> /dev/null)"

	# find the most recent "version" tag in the current history
	VCS_MOST_RECENT_TAGGED_VERSION=
	local tag_skip=0
	# to avoid an infinite loop (if Git repo is in an unexpected state), set an upper bound
	while [ "$tag_skip" -le "50000" ]; do
		# Important: the "local" variable declaration must be on its own line so that the exit status
		# of the git command can be retrieved
		local revision
		local exstatus
		local current_tag
		revision="$(git rev-list --tags --skip=${tag_skip} --max-count=1 2> /dev/null)"
		exstatus=${?}
		if [ $exstatus -ne 0 ] || [ -z "$revision" ]; then
			VCS_MOST_RECENT_TAGGED_VERSION=
			break
		fi
		current_tag="$(git describe --abbrev=0 --tags "${revision}" 2> /dev/null)"
		exstatus=${?}
		if [ $exstatus -ne 0 ] || [ -z "$current_tag" ]; then
			# Reached end of tags (or git returned an error code)
			VCS_MOST_RECENT_TAGGED_VERSION=
			break
		fi
		if canExtractVersionNumberFromGitTag "${current_tag}"; then
			# Found a tag that looks like a version number
			VCS_MOST_RECENT_TAGGED_VERSION="${current_tag}"
			break
		fi
		tag_skip=$(($tag_skip + 1))
	done
	if [ -n "${VCS_MOST_RECENT_TAGGED_VERSION}" ]; then
		VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION="$(git rev-list --count ${VCS_MOST_RECENT_TAGGED_VERSION}.. 2> /dev/null)"
	else
		# No version tag detected in Git history. VCS_MOST_RECENT_TAGGED_VERSION and VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION will be empty
		VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION=
	fi

	# get the commit count on this branch *since* the branch from master
	VCS_BRANCH_COMMIT_COUNT="$(git rev-list --count master.. 2> /dev/null)"

	# get the commit count on master until the branch
	local first_new_commit_on_branch_since_master
	local result
	VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH=
	first_new_commit_on_branch_since_master="$(git rev-list master.. | tail -n 1)"
	result=${?}
	if [ ${result} -eq 0 ]; then
		if [ -z "${first_new_commit_on_branch_since_master}" ] && [ -n "${VCS_BRANCH_COMMIT_COUNT}" ] && [ "${VCS_BRANCH_COMMIT_COUNT}" -eq 0 ]; then
			# The call succeeded, but git returned nothing
			# The number of commits since master is 0, so set VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH
			# to be equal to VCS_COMMIT_COUNT
			VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH="${VCS_COMMIT_COUNT}"
		else
			VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH="$(git rev-list --count ${first_new_commit_on_branch_since_master}^ 2> /dev/null)"
		fi
	fi

	# get the most recent commit date
	VCS_MOST_RECENT_COMMIT_DATE="$(git log -1 --format=%cI)"
	VCS_MOST_RECENT_COMMIT_YEAR="$(git log -1 --format=%cI | cut -d "-" -f1)"
}

# For Travis-CI builds
travisCIBuild() {
	# Information must be extracted from a combination of Travis-set environment
	# variables and other sources

	# Start by calling gitRepo, since certain values should be obtained directly from git
	# IMPORTANT: Since Travis uses a shallow clone by default, unshallow must be performed before
	#            this script is run so that the correct values can be obtained by gitRepo().
	gitRepo

	# The full revision hash
	VCS_FULL_HASH="${TRAVIS_COMMIT}"
	# The short hash
	VCS_SHORT_HASH="$(echo "${VCS_FULL_HASH}" | cut -b 1-7)"

	# Current branch
	VCS_BRANCH="${TRAVIS_BRANCH}"
	if [ -n "${TRAVIS_PULL_REQUEST_BRANCH}" ]; then
		# When triggered by a pull request, TRAVIS_BRANCH is set to the *target* branch name
		# But we want the source branch name, so use TRAVIS_PULL_REQUEST_BRANCH
		VCS_BRANCH="${TRAVIS_PULL_REQUEST_BRANCH}"
	fi

	# Check if we are on a tag
	VCS_TAG=
	if [ -n "${TRAVIS_TAG}" ]; then
		VCS_TAG="${TRAVIS_TAG}"

		# When on a tag, clear VCS_BRANCH
		VCS_BRANCH=
	fi

	VCS_EXTRA=
}


# Functions to output data in different formats.
# For header output
hOutput() {
	cat > "${TARGETFILE}" << EOF
/* Generated by autorevision - do not hand-hack! */
#ifndef AUTOREVISION_H
#define AUTOREVISION_H

#define VCS_TYPE		"${VCS_TYPE}"
#define VCS_BASENAME	"${VCS_BASENAME}"
#define VCS_BRANCH		"${VCS_BRANCH}"
#define VCS_TAG			"${VCS_TAG}"
#define VCS_EXTRA       "${VCS_EXTRA}"


#define VCS_FULL_HASH		"${VCS_FULL_HASH}"
#define VCS_SHORT_HASH		"${VCS_SHORT_HASH}"

#define VCS_WC_MODIFIED		${VCS_WC_MODIFIED}

#define VCS_COMMIT_COUNT	${VCS_COMMIT_COUNT}
#define VCS_MOST_RECENT_TAGGED_VERSION	"${VCS_MOST_RECENT_TAGGED_VERSION}"
#define VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION	${VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION}
#define VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH	${VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH}
#define VCS_BRANCH_COMMIT_COUNT	${VCS_BRANCH_COMMIT_COUNT}
#define VCS_MOST_RECENT_COMMIT_DATE "${VCS_MOST_RECENT_COMMIT_DATE}"
#define VCS_MOST_RECENT_COMMIT_YEAR "${VCS_MOST_RECENT_COMMIT_YEAR}"

#endif

/* end */
EOF
}

# A header output for use with xcode to populate info.plist strings
xcodeOutput() {
	cat > "${TARGETFILE}" << EOF
/* Generated by autorevision - do not hand-hack! */
#ifndef AUTOREVISION_H
#define AUTOREVISION_H

#define VCS_TYPE		${VCS_TYPE}
#define VCS_BASENAME	${VCS_BASENAME}
#define VCS_BRANCH		${VCS_BRANCH}
#define VCS_TAG			${VCS_TAG}
#define VCS_EXTRA       ${VCS_EXTRA}

#define VCS_FULL_HASH		${VCS_FULL_HASH}
#define VCS_SHORT_HASH		${VCS_SHORT_HASH}

#define VCS_WC_MODIFIED		${VCS_WC_MODIFIED}

#define VCS_COMMIT_COUNT	${VCS_COMMIT_COUNT}
#define VCS_MOST_RECENT_TAGGED_VERSION	${VCS_MOST_RECENT_TAGGED_VERSION}
#define VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION	${VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION}
#define VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH	${VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH}
#define VCS_BRANCH_COMMIT_COUNT	${VCS_BRANCH_COMMIT_COUNT}
#define VCS_MOST_RECENT_COMMIT_DATE ${VCS_MOST_RECENT_COMMIT_DATE}
#define VCS_MOST_RECENT_COMMIT_YEAR ${VCS_MOST_RECENT_COMMIT_YEAR}

## AUTOINSERT MARK

#endif

/* end */
EOF
}

# For bash output
shOutput() {
	cat > "${TARGETFILE}" << EOF
# Generated by autorevision - do not hand-hack!

VCS_TYPE="${VCS_TYPE}"
VCS_BASENAME="${VCS_BASENAME}"
VCS_BRANCH="${VCS_BRANCH}"
VCS_TAG="${VCS_TAG}"
VCS_EXTRA="${VCS_EXTRA}"

VCS_FULL_HASH="${VCS_FULL_HASH}"
VCS_SHORT_HASH="${VCS_SHORT_HASH}"

VCS_WC_MODIFIED=${VCS_WC_MODIFIED}

VCS_COMMIT_COUNT=${VCS_COMMIT_COUNT}
VCS_MOST_RECENT_TAGGED_VERSION="${VCS_MOST_RECENT_TAGGED_VERSION}"
VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION=${VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION}
VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH=${VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH}
VCS_BRANCH_COMMIT_COUNT=${VCS_BRANCH_COMMIT_COUNT}
VCS_MOST_RECENT_COMMIT_DATE="${VCS_MOST_RECENT_COMMIT_DATE}"
VCS_MOST_RECENT_COMMIT_YEAR="${VCS_MOST_RECENT_COMMIT_YEAR}"

# end
EOF
}
# Detect and collect repo data.
if [ -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
	# When requested only read from the cache to populate our symbols.
	. "${CACHEFILE}"
elif [ -n "${TRAVIS}" ] && [ "${TRAVIS}" == "true" ]; then
	# Autorevision is being executed from a Travis-CI build
	travisCIBuild
elif [ ! -z "$(git rev-parse HEAD 2>/dev/null)" ]; then
	gitRepo
elif [ -f "${CACHEFILE}" ]; then
	# We are not in a repo; try to use a previously generated cache to populate our symbols.
	. "${CACHEFILE}"
	# Do not overwrite the cache if we know we are not going to write anything new.
	CACHEFORCE="1"
else
	echo "error: No repo, cache, or other data source detected." 1>&2
	exit 1
fi


# -s output is handled here.
if [ ! -z "${VAROUT}" ]; then
	if [ "${VAROUT}" = "VCS_TYPE" ]; then
		echo "${VCS_TYPE}"
	elif [ "${VAROUT}" = "VCS_BASENAME" ]; then
		echo "${VCS_BASENAME}"
	elif [ "${VAROUT}" = "VCS_BRANCH" ]; then
		echo "${VCS_BRANCH}"
	elif [ "${VAROUT}" = "VCS_TAG" ]; then
		echo "${VCS_TAG}"
	elif [ "${VAROUT}" = "VCS_EXTRA" ]; then
		echo "${VCS_EXTRA}"
	elif [ "${VAROUT}" = "VCS_FULL_HASH" ]; then
		echo "${VCS_FULL_HASH}"
	elif [ "${VAROUT}" = "VCS_SHORT_HASH" ]; then
		echo "${VCS_SHORT_HASH}"
	elif [ "${VAROUT}" = "VCS_WC_MODIFIED" ]; then
		echo "${VCS_WC_MODIFIED}"
	elif [ "${VAROUT}" = "VCS_COMMIT_COUNT" ]; then
		echo "${VCS_COMMIT_COUNT}"
	elif [ "${VAROUT}" = "VCS_MOST_RECENT_TAGGED_VERSION" ]; then
		echo "${VCS_MOST_RECENT_TAGGED_VERSION}"
	elif [ "${VAROUT}" = "VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION" ]; then
		echo "${VCS_COMMIT_COUNT_SINCE_MOST_RECENT_TAGGED_VERSION}"
	elif [ "${VAROUT}" = "VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH" ]; then
		echo "${VCS_COMMIT_COUNT_ON_MASTER_UNTIL_BRANCH}"
	elif [ "${VAROUT}" = "VCS_BRANCH_COMMIT_COUNT" ]; then
		echo "${VCS_BRANCH_COMMIT_COUNT}"
	elif [ "${VAROUT}" = "VCS_MOST_RECENT_COMMIT_DATE" ]; then
		echo "${VCS_MOST_RECENT_COMMIT_DATE}"
	elif [ "${VAROUT}" = "VCS_MOST_RECENT_COMMIT_YEAR" ]; then
		echo "${VCS_MOST_RECENT_COMMIT_YEAR}"
	else
		echo "error: Not a valid output symbol." 1>&2
		exit 1
	fi
fi

# Detect requested output type and use it.
if [ ! -z "${AFILETYPE}" ]; then
	if [ "${AFILETYPE}" = "h" ]; then
		hOutput
	elif [ "${AFILETYPE}" = "xcode" ]; then
		xcodeOutput
	elif [ "${AFILETYPE}" = "sh" ]; then
		shOutput
	else
		echo "error: Not a valid output type." 1>&2
		exit 1
	fi
fi


# If requested, make a cache file.
if [ ! -z "${CACHEFILE}" ] && [ ! "${CACHEFORCE}" = "1" ]; then
	TARGETFILE="${CACHEFILE}"
	shOutput
fi
