#!/bin/sh -efu
#
# Copyright (C) 2006-2007  Alexey Gladkov <legion@altlinux.org>
# Copyright (C) 2006-2007  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2006  Sergey Vlasov <vsu@altlinux.org>
#
# gear-update updates subdirectory from source directory or archive file.
#
# 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

show_help()
{
	cat <<EOF
$PROG - update subdirectory from source directory or archive file.

Usage: $PROG [Options] <source> <subdirectory>

Options:

  --ignore-exclude    ignore .gitignore and .git/info/exclude;
  -s,--subdir=DIR     extract specified directory name from archive;
  -t,--type=TYPE      define source type: 'dir', 'tar' or 'zip';
  -f,--force          remove files from <subdirectory> even
                      if untracked or modified files found;
  -v,--verbose        print a message for each action;
  -V,--version        print program version and exit;
  -h,--help           show this text and exit.

Report bugs to http://bugs.altlinux.ru/

EOF
	exit
}

print_version()
{
	cat <<EOF
$PROG version $PROG_VERSION
Written by Alexey Gladkov <legion@altlinux.org>

Copyright (C) 2006-2007  Alexey Gladkov <legion@altlinux.org>
Copyright (C) 2006-2007  Dmitry V. Levin <ldv@altlinux.org>
Copyright (C) 2006  Sergey Vlasov <vsu@altlinux.org>
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
}

source_type=
get_type_by_source()
{
	if [ -d "$source" ]; then
		source_type=dir
		return
	fi

	local file_type
	file_type="$(file -b "$source")"
	case "$file_type" in
		*tar\ archive*)
			source_type=tar
			return
			;;
		*Zip\ archive*)
			source_type=zip
			return
			;;
	esac

	file_type="$(file -bz "$source")"
	case "$file_type" in
		*tar\ archive*)
			source_type=tar
			return
			;;
	esac

	fatal "$source: unrecognized source type: $file_type"
}

subdir=
validate_subdir()
{
	if [ "$source_type" = dir ]; then
		[ -z "$subdir" -o "$subdir" = "$source" ] ||
			fatal "Invalid subdir \`$subdir' for directory source \`$source'"
		subdir="$source"
		return 0
	fi

	local source_ls_cmd
	case "$source_type" in
		tar) source_ls_cmd='tar -tf'    ;;
		zip) source_ls_cmd='zipinfo -1 --' ;;
		*) fatal "$source_type: unknown source type" ;;
	esac

	if [ -z "$subdir" ]; then
		subdir="$($source_ls_cmd "$source")"
		subdir="$(printf %s "$subdir" |
			  sed 's#\(\([./]\+\)\?[^/]\+\)/.*#\1#g' |
			  grep '^[^[:space:]]' |
			  sort -u)"
	fi

	[ -n "$subdir" ] ||
		fatal "$source: subdirectory not found"
	[ `printf %s "$subdir" |wc -l` -eq 0 ] ||
		fatal "More than one subdirectory specified: $(printf %s "$subdir" |tr -s '[:space:]' ' ')"

	local quoted_subdir
	quoted_subdir="$(quote_sed_regexp "$subdir")"
	$source_ls_cmd "$source" |grep -qs "^$quoted_subdir\(/\|\$\)" ||
		fatal "$source: directory \`$subdir' not found in archive"
}

canon_destdir=
validate_destdir()
{
	[ "$#" -eq 2 ] ||
		fatal "validate_destdir: invalid ($#) number of arguments"
	local src_dir="$1"; shift
	local dst_dir="$1"; shift
	if [ "$dst_dir" = '.' -a -d '.git' -a -d "$src_dir/.git" ]; then
		fatal "$canon_destdir/.git: Cowardly refusing to replace git directory"
	fi
}

workdir=
destdir=
exit_handler()
{
	local rc=$?
	trap - EXIT
	[ -z "$workdir" ] ||
		rm -rf -- "$workdir"
	if [ "$rc" -gt 0 ]; then
		# Rollback actions
		if [ -d "$destdir" ]; then
			find "$destdir" -mindepth 1 -maxdepth 1 \! -name '.git' -print0 |
				xargs -r0 rm $verbose -rf --
		else
			rm $verbose -f -- "$destdir"
		fi
		mkdir -p -- "$destdir"
		cd "$destdir"
		git checkout-index -a
		msg_info 'failed to update target directory'
	fi
	exit $rc
}

TEMP=`getopt -n $PROG -o f,t:,s:,v,V,h -l ignore-exclude,force,type:,subdir:,verbose,version,help -- "$@"` || show_usage
eval set -- "$TEMP"

force=
ignore_exclude=
while :; do
	case "$1" in
	    --ignore-exclude) ignore_exclude=1
		;;
	    -f|--force) force=1
		;;
	    -t|--type) shift; source_type="$1"
		;;
	    -s|--subdir) shift; subdir="$1"
		;;
	    -v|--verbose) verbose=-v
		;;
	    -V|--version) print_version
		;;
	    -h|--help) show_help
		;;
	    --) shift; break
		;;
	    *) fatal "unrecognized option: $1"
		;;
	esac
	shift
done

[ "$#" -ge 2 ] ||
	show_usage 'Not enough arguments.'
[ "$#" -eq 2 ] ||
	show_usage 'Too many arguments.'

source="$(readlink -ev "$1")"; shift
destdir="$(readlink -ev "$1")"; shift
canon_destdir="$destdir"

[ "$source" != "$destdir" ] ||
	fatal "\`$source' and \`$destdir' are the same place"
[ -d "$destdir" ] ||
	fatal "$destdir: not a directory"

# Change to toplevel directory
chdir_to_toplevel
topdir="$(readlink -ev .)"

# Make destdir relative
destdir="$destdir/"
destdir="${destdir#$topdir/}"
[ "$destdir" = "${destdir#/}" ] ||
	fatal "$destdir: directory out of \`$topdir' tree."
[ -n "$destdir" ] &&
	destdir="${destdir%/}" ||
	destdir='.'
git rm -n -r -- "$destdir" >/dev/null

if [ -z "$force" ]; then
	out="$(git diff --name-only -- "$destdir" &&
	       git ls-files --directory --others --exclude-per-directory=.gitignore -- "$destdir")"
	[ -z "$out" ] ||
		fatal "$destdir: untracked or modified files found"
fi

[ -n "$source_type" ] || get_type_by_source
validate_subdir

# Trap for rollback
trap exit_handler HUP INT QUIT TERM EXIT

# Remove $destdir
find "$destdir" -mindepth 1 -maxdepth 1 \! -name '.git' -print0 |
	xargs -r0 rm $verbose -rf --
[ "$destdir" = '.' ] ||
	rmdir $verbose -- "$destdir"

# Copy new sources
case "$source_type" in
	tar)
		workdir="$(mktemp -d "$PROG.XXXXXXXXX")"
		tar -x -f "$source" -C "$workdir" -- "$subdir/"
		validate_destdir "$workdir/$subdir" "$destdir"
		mv -f -- "$workdir/$subdir" "$destdir"
		;;
	zip)
		workdir="$(mktemp -d "$PROG.XXXXXXXXX")"
		unzip -q "$source" "$subdir/*" -d "$workdir"
		validate_destdir "$workdir/$subdir" "$destdir"
		mv -f -- "$workdir/$subdir" "$destdir"
		;;
	dir)
		validate_destdir "$subdir" "$destdir"
		cp -a -- "$subdir" "$destdir"
		;;
esac
[ -d "$destdir" ] ||
	fatal "$destdir: result of extraction is not a directory"

if [ -n "$ignore_exclude" ]; then
	git ls-files -z -- "$destdir" |
		git update-index --remove --force-remove -z --stdin
	git ls-files -z --others --modified -- "$destdir" |
		git update-index --add ${verbose:+--verbose} -z --stdin
else
	git rm -f -r --cached -- "$destdir" >/dev/null
	git add $verbose -- "$destdir"
fi
