#!/usr/bin/env bash
#
# Show status of your working tree.
# Copyright (c) Petr Baudis, 2005
# Copyright (c) Pavel Roskin 2005
#
# The output includes the list of branches and merge status.
# Current branch is marked by ">", remote branches are marked by "R".
#
# Then, the files in the working tree are printed out. The output has
# the following format:
#
#	<status flag> <file>
#
# where '<status flag>' can be one of the following:
#
# ?::
#	'<file>' is unknown.
# A::
#	'<file>' has been added.
# D::
#	'<file>' has been deleted.
# !::
#	'<file>' is gone from your working copy but not deleted by cg-rm.
# M::
#	'<file>' has been touched or modified.
# m::
#	'<file>' has been touched or modified, but will not be automatically
#	committed the next time you call cg-commit. This is used during a
#	merge to mark files which contained local changes before the merge.
#
# OPTIONS
# -------
# If neither -g or -w is passed, both is shown; otherwise, only the
# corresponding parts are shown.
#
# -g::
#	Show the GIT repository information.
#
# -n::
#	Do not show status flags. This is probably useful only when you filter
#	the flags for a single specific flag using the '-s' option.
#
# -s STATUS::
#	Show only files with the given status flag, e.g. '-s D'. You can list
#	multiple flags ('-s MmA') to filter for all of them.
#
# -w::
#	Show the working tree file list.
#
# -x::
#	Don't exclude any files from listing.
#
# DIRPATH::
#	Path to the directory to use as the base for the working tree
#	file list (instead of the current directory).
#
# FILES
# -----
# $GIT_DIR/info/exclude::
#	If the file exists it will be used to prune which files to
#	show status for. The format is similar to the `dontdiff` file;
#	each line contains a pattern for a file or group of files
#	to exclude.
#
# $TREE_DIR/.gitignore::
#	.gitignore in the working tree will be used as an exclude file.
#	The excludes are applied from the project root approaching the
#	current subdirectory.

USAGE="cg-status [-g] [[-n] -s STATUS] [-w] [-x] [DIRPATH]"

. ${COGITO_LIB}cg-Xlib || exit 1


should_show_flag() {
	[[ -z "$flagfilter" || "$flagfilter" == *"$1"* ]]
}


gitstatus=
workstatus=
exclude=yes
flagfilter=
noflags=
while optparse; do
	if optparse -g; then
		gitstatus=1
	elif optparse -n; then
		noflags=1
	elif optparse -s=; then
		flagfilter="$OPTARG"
		echo "$flagfilter" | grep -qx '[a-zA-Z?!]*' \
			|| die "invalid -s status flag"
	elif optparse -w; then
		workstatus=1
	elif optparse -x; then
		exclude=no
	else
		optfail
	fi
done
if [ ! "$gitstatus" ] && [ ! "$workstatus" ]; then
	gitstatus=1
	workstatus=1
fi



if [ "$gitstatus" ]; then
	mkdir -p $_git/refs/heads
	[ "$(find $_git/refs/heads -follow -type f)" ] \
	       || die "List of heads is empty."


	[ -s "$_git/branch-name" ] && echo "Branch (informal): $(cat "$_git/branch-name")"

	if [ -s "$_git/head-name" ]; then
		headsha1=$(cat "$_git/$(git-symbolic-ref HEAD)")
		echo "Seeked from head: $(cat "$_git/head-name")"
		echo "Seeked at commit: $headsha1"
		echo
	fi

	echo "Heads:"
	find "$_git/refs/heads" ! -type d | sort | while read head; do 
		headsha1=$(cat "$head")
		headname=${head#$_git/refs/heads/}
		[ "$headname" = "cg-seek-point" ] && continue
		cf=" "; rf=" "
		[ "$headname" = "$_git_head" ] && cf=">"
		[ -s "$_git/branches/$headname" ] && rf="R"
		echo -e "  $rf$cf$headname\t$headsha1"
	done

	if [ -s "$_git/merging" ]; then
		tmp=$(cat "$_git/merging")
		echo
		echo "Merging: $(cat "$_git/merging") ($(cat "$_git/merging-sym"))"
		echo "Merge base: $(cat "$_git/merge-base")"
		[ -s "$_git/squashing" ] && echo "Squash-merge."
	fi
	if [ -s "$_git/commit-ignore" ]; then
		echo "Files not to be committed now (contained local changes before the merge):"
		sed 's/^/    /' "$_git/commit-ignore"
	fi

	if [ -s "$_git/blocked" ]; then
		echo
		echo "Changes recording BLOCKED:"
		sed 's/^/    /' "$_git/blocked"
	fi

	if [ ! -s "$_git/$(git-symbolic-ref HEAD)" ]; then
		echo
		echo "Before initial commit."
	fi
fi



if [ "$gitstatus" ] && [ "$workstatus" ]; then
	echo
fi



if [ "$workstatus" ]; then
	git-update-index --refresh > /dev/null

	basepath="$_git_relpath"
	[ "${ARGS[0]}" ] && basepath="$(echo "${ARGS[0]}" | normpath)"

	should_show_flag '?' &&
		list_untracked_files $exclude | tr '\0' '\n' |
		if [ "$basepath" ]; then
			while IFS=$'' read path; do
				[ x"${path#$basepath}" != x"$path" ] &&
					echo "${path#$basepath}"
			done
		else
			cat
		fi |
		sed 's,^,? ,'

	if [ ! -s "$_git/$(git-symbolic-ref HEAD)" ]; then
		# Initial commit
		should_show_flag 'A' && git-ls-files | sed 's,^,A ,'
	else
		commitignore=
		[ -s "$_git/commit-ignore" ] && commitignore=1

		git-diff-index HEAD $basepath | cut -f5- -d' ' | 
		while IFS=$'\t' read -r mode file; do
			if [ "$mode" = D ]; then
				[ "$(git-diff-files "$file")" ] && mode=!
			elif [ "$mode" = M ] && [ "$commitignore" ]; then
				fgrep -qx "$file" "$_git/commit-ignore" && mode=m
			fi
			[ "$basepath" ] && file="${file#$basepath}"
			should_show_flag "$mode" && echo "$mode $file"
		done
	fi
fi | if [ "$noflags" ]; then
	sed 's/^. //'
else
	cat
fi
