#!/usr/pkg/bin/bash
#
# Restore files in the working tree to the given state
# Copyright (c) Petr Baudis, 2005
#
# Restore given files to their original state.  It recovers any files
# (or files passed as arguments to the command, respectively) removed
# locally whose removal was not recorded by `cg-rm`.
#
# If passed the -f parameter, it restores the files to their state
# as of the last commit (including bringing files removed with
# `cg-rm` back to life).
#
# If passed the -r parameter, it will not restore the file as of the
# last commit, but to the state in the given commit, tree, or blob.
# The list of files is mandatory in this case.
#
# For the "restore-to-last-commit" usage, this command is
# complementary to the `cg-reset` command, which forcefully abandons
# all the changes in the working tree and restores everything to
# a proper state (including unseeking, cancelling merge in progress
# and rebuilding indexes).
#
# OPTIONS
# -------
# -f:: Undo local changes since last commit
#	Restore even locally modified files to the version as of
#	the last commit. Take care!
#
# -r COMMIT_ID:: Restore files to given COMMIT_ID
# -r TREE_ID:: Restore files to given TREE_ID
# -r BLOB_ID:: Restore files to given BLOB_ID
#	Restore the file to the state appropriate to the given ID.
#	The list of files to recover is mandatory in this case.

# Testsuite: Marginal (part of t9202-merge-on-dirty)

USAGE="cg-restore [-f] [-r ID] [FILE]..."

. "${COGITO_LIB:-/usr/pkg/lib/cogito/}"cg-Xlib || exit 1

force=
objid=
while optparse; do
	if optparse -f; then
		force=-f
	elif optparse -r=; then
		objid="$(cg-object-id -n "${OPTARG}")" || exit 1
	else
		optfail
	fi
done

ret=0

if [ "$ARGS" ]; then
	if [ "$objid" ]; then
		objtype="$(git-cat-file -t "$objid")"
		if [ "$objtype" = "commit" ]; then
			objid="$(cg-object-id -t "$objid")"
			objtype="tree"
		fi
	else
		objid="$(cg-object-id -t)"
		objtype="tree"
	fi

	files=()
	if [ -n "$force" ]; then
		for file in "${ARGS[@]}"; do
			files[${#files[@]}]="${_git_relpath}$file"
		done
	else
		# Not forcing, filter out existing files
		for file in "${ARGS[@]}"; do
			if [ -e "${_git_relpath}$file" ]; then
				echo "Error: File $file already exists; use -f to override" >&2
				ret=2
				continue
			fi
			files[${#files[@]}]="${_git_relpath}$file"
		done
		[ ${#files[@]} -ge 1 ] || die "no files suitable for restoring left"
	fi

	if [ "$objtype" = "tree" ]; then
		TMPFILE="$(mktemp -t gitrestore.XXXXXX)" || exit 1
		if ! git-ls-tree -r "$objid" "${files[@]}" |
			sed -ne 's/^\([0-7]*\) blob \(.*\)$/\1 \2/p' |
			( ret=0; while read mode id name; do
				echo "Restoring file ${name#$_git_relpath}"
				# TODO: Use git-update-index --index-info when we
				# will depend on git new enough. --pasky
				if ! git-update-index --add --cacheinfo "$mode" "$id" "$name"; then
					echo "Error: Cannot mark ${name#$_git_relpath} for update" >&2
					ret=1
					continue
				else
					echo "$name" >>$TMPFILE
				fi
			done; exit $ret ); then
				ret=$?
		# When we'll do --index-info which should be atomic:
		#|| {
		#	echo "Fatal: git-update-index failed, cancelling the whole operation (restored nothing)" >&2
		#	exit $?
		#}
		fi
		cat "$TMPFILE" | tr '\n' '\0' | /usr/bin/xargs -0 git-checkout-index -u $force -- || ret=1
		rm "$TMPFILE"

	elif [ "$objtype" = "blob" ]; then
		[ "${#files[@]}" -gt 1 ] && warn "restoring multiple files to a single blob"
		for file in "${files[@]}"; do
			echo "Restoring file ${file#$_git_relpath}"
			warn "file ${file#$_git_relpath} likely will not have correct permissions"
			git-cat-file blob "$objid" >"$file"
			git-update-index -- "$file" || ret=1
		done
	fi

else # no arguments - much weaker
	[ "$objid" ] && die "you need to pass an explicit list of files to -r"
	[ "$_git_relpath" ] && die "cannot restore files en masse in subdirectories yet"
	if [ "$(git-ls-files --deleted)" ]; then
		git-ls-files --deleted | sed "s/^/Restoring file /"
	fi
	git-checkout-index -u -q -a $force
fi

exit $ret
