#!/bin/sh -efu
#
# Copyright (C) 2011  Aleksey Avdeev <solo@altlinux.ru>
#
# Manage restored tags in the package repository.
#
# This file 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

. gear-sh-functions

PROG_VERSION="@VERSION@"

tmpdir=
cleanup_handler()
{
	[ -z "$tmpdir" ] || rm -rf -- "$tmpdir"
}

print_version()
{
	cat <<EOF
$PROG version $PROG_VERSION
Written by Aleksey Avdeev <solo@altlinux.ru>

Copyright (C) 2011  Aleksey Avdeev <solo@altlinux.ru>
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
EOF
	exit
}

show_help()
{
	cat <<EOF
$PROG - manage restored tags in the package repository

Usage: $PROG [options]
or: $PROG [options] --list [--no-type] [--no-sha1] [--no-name]

Options:
  --no-tags                 do not process the tags;
  --no-branches             do not process the branches;
  -l, --list                print the names of all saved tags or brances
                            (in the format: <type>\\t<sha1>\\t<name>) and exit;
      --no-type             do not print types;
      --no-sha1             do not print sha1;
      --no-name             do not print names;
  -f, --force               modify existing tags and branches;
  -v, --verbose             print a message for each action;
  -q, --quiet               do not print a message for each action;
  -V, --version             print program version and exit;
  -h, --help                show this text and exit.

Report bugs to http://bugzilla.altlinux.org/

EOF
	exit
}

get_tag_object()
{
	sed -r -n '
/^[[:space:]]*$/q
/^object[[:space:]]+[[:xdigit:]]+[[:space:]]*$/s/^object[[:space:]]+([[:xdigit:]]+)[[:space:]]*$/\1/p
' "$tag_dir/$1"
}

get_tag_type()
{
	sed -r -n '
/^[[:space:]]*$/q
/^type[[:space:]]+[a-z]+[[:space:]]*$/s/^type[[:space:]]+([a-z]+)[[:space:]]*$/\1/p
' "$tag_dir/$1"
}

get_tag_tag()
{
	sed -r -n '
/^[[:space:]]*$/q
/^tag[[:space:]]+[^[:space:]]+(.*[^[:space:]])?[[:space:]]*$/s/^tag[[:space:]]+([^[:space:]]+(.*[^[:space:]])?)[[:space:]]*$/\1/p
' "$tag_dir/$1"
}

check_tagname()
{
	git tag | grep -q -m1 "^$1\$" || return 1
}

check_tagtype()
{
	[ "`get_tag_type "$1"`" = "tag" ] || return 1
}

list_print()
{
	local type=
	local sha1=
	local name=
	local format="%s"
	if [ -n "$type_print" ]; then
		type="$1"
	fi

	if [ -n "$sha1_print" ]; then
		sha1="$2"

		if [ -n "$type_print" ]; then
			format="$format\t%s"
		else
			format="$format%s"
		fi
	else
		format="$format%s"
	fi

	if [ -n "$name_print" ]; then
		name="$3"

		if [ -n "$type_print$sha1_print" ]; then
			format="$format\t%s\n"
		else
			format="$format%s\n"
		fi
	else
		format="$format%s\n"
	fi

	printf "$format" "$type" "$sha1" "$name"
}

list_print_tag()
{
	list_print "tag" "$1" "$2"
}

list_print_branch()
{
	list_print "branch" "$1" "$2"
}

list_print_tags()
{
	if [ -r "$tag_dir/$1" ]; then
		local object="`get_tag_object "$1"`"
		if check_tagtype "$1"; then
			list_print_tags "$object"
		fi

		local tagname=
		if [ "$#" -gt "1" ] && [ -n "$2" ]; then
			tagname="$2"
		else
			tagname="`get_tag_tag "$1"`"
		fi

		list_print_tag "$1" "$tagname"
	else
		fatal "File $tag_dir/$1 is not readable!"
	fi
}

list_all()
{
	# Check $tag_dir/list
	if [ ! -r "$tag_dir/list" ]; then
		fatal "File $tag_dir/list is not readable!"
	fi

	local sha1=
	local name=
	verbose "Read $tag_dir/list"
	while read -r sha1 name; do
		verbose "Processing of the tag or branch \"$name\" ($sha1)"
		if [ -r "$tag_dir/$sha1" ]; then
			verbose "  \"$name\" this tag"
			if [ -n "$tags_processing" ]; then
				list_print_tags "$sha1" "$name"
			else
				verbose "Do not handle tags!"
			fi
		else
			verbose "  \"$name\" this branch"
			if [ -n "$branches_processing" ]; then
				list_print_branch "$sha1" "$name"
			else
				verbose "Do not handle branches!"
			fi
		fi
	done <"$tag_dir/list"
}

restore_tag()
{
	if [ -r "$tag_dir/$1" ]; then
		if ! git cat-file -e "$1"; then
			verbose "  create tag $1"
			local object="`get_tag_object "$1"`"
			if ! git cat-file -e "$object"; then
				fatal "Object $object right for the tag $1 was not found!"
			fi
			git mktag < "$tag_dir/$1"
		fi

		local tagname=
		if [ "$#" -gt "1" ] && [ -n "$2" ]; then
			tagname="$2"
		else
			tagname="`get_tag_tag "$1"`"
		fi

		if check_tagname "$tagname"; then
			verbose "  tag \"$tagname\" already exists!"
			if [ -z "$force" ]; then
				return
			fi
		fi

		verbose "  create tag \"$tagname\""
		git update-ref "refs/tags/$tagname" "$1"
	else
		fatal "File $tag_dir/$1 is not readable!"
	fi
}

TEMP=`getopt -n "$PROG" -o l,f,$getopt_common_opts -l no-branches,no-tags,list,no-type,no-sha1,no-name,force,$getopt_common_longopts -- "$@"` ||
	show_usage
eval set -- "$TEMP"

tags_processing="yes"
branches_processing="yes"
list=
type_print="yes"
sha1_print="yes"
name_print="yes"
force=
verify=
verbose=
while :; do
	case "$1" in
		--) shift; break
			;;
		--no-tags) tags_processing=
			;;
		--no-branches) branches_processing=
			;;
		-l|--list) list="yes"
			;;
		--no-type)
			list="yes"
			type_print=
			;;
		--no-sha1)
			list="yes"
			sha1_print=
			;;
		--no-name)
			list="yes"
			name_print=
			;;
		-f|--force) force="-f"
			;;
		-q|--quiet) verbose=
			;;
		-v|--verbose) verbose=1
			;;
		-h|--help) show_help
			;;
		-V|--version) print_version
			;;
		*) fatal "Unrecognized option: $1"
			;;
	esac
	shift
done

# Save git directory for future use.
git_dir="$(git rev-parse --git-dir)"
git_dir="$(readlink -ev -- "$git_dir")"

# Change to toplevel directory.
chdir_to_toplevel

tmpdir="$(mktemp -dt "$PROG.XXXXXXXX")"
workdir="$tmpdir/work"
mkdir "$workdir"
tmp_tag_dir="$tmpdir/tags"
mkdir "$tmp_tag_dir"

if [ -z "$tags_processing$branches_processing" ]; then
	verbose "Needless to handle!"
	exit 0
fi

find_rules_in_cwd
find_tags_in_cwd

head_branch="`cat "$git_dir/HEAD" | sed -r 's@[[:space:]]*ref:[[:space:]]+(refs/heads/|)@@'`"
head_commit="`git rev-parse "$head_branch"`"

if [ -n "$list" ]; then
	if [ -n "$type_print$sha1_print$name_print" ]; then
		list_all
	else
		verbose "Nothing to print!"
	fi
else
	type=
	sha1=
	name=
	head_newcommit=
	list_all | while read -r type sha1 name; do
		case "$type" in
			tag)
				verbose "  \"$name\" this tag"
				restore_tag "$sha1" "$name"
				;;
			branch)
				verbose "  \"$name\" this branch"
				git cat-file -e "$sha1" || fatal "Commit $sha1 is not in the repositories!"

				if [ "$name" = "$head_branch" ]; then
					verbose "  \"$name\" the same name as the current branch!"
					if [ -n "$force" ]; then
						head_newcommit="$sha1"
					fi
					continue
				fi

				if git branch | grep -E -q -m1 "^\\*?[[:space:]]+$name\$"; then
					verbose "  branch \"$name\" already exists!"
					if [ -z "$force" ]; then
						continue
					fi
				fi

				verbose "  create branch \"$name\""
				git branch $force "$name" "$sha1" || fatal "Can not create a branch \"$name\"!"
				;;
			*) fatal "Unrecognized type: $type"
				;;
		esac
	done

	if [ -n "$head_newcommit" ]; then
		verbose "Set current branch \"$head_branch\" to $head_newcommit"
		git reset --hard "$head_newcommit"
	fi
fi
