#!/bin/sh
set -e

# Keep the sort order the same at all times.
LC_COLLATE=C
export LC_COLLATE
unset LC_ALL

filter_ignore() {
	case "$VCS" in
		darcs) ignorefile=.darcsignore ;;
		git)   ignorefile=.gitignore ;;
	esac

	if [ -n "$ignorefile" ] && [ -e "$ignorefile" ]; then
		if command -v mktemp >/dev/null; then
			tempfile="mktemp -t etckeeper-$VCS.XXXXXXXXXX"
		elif command -v tempfile >/dev/null; then
			tempfile="tempfile"
		else
			echo "etckeeper warning: can't find tempfile or mktemp" >&2
			exit 1
		fi
		listfile="$($tempfile)"
		case "$VCS" in
			darcs)
				LC_CTYPE=C grep -v '^[[:space:]]*\(#\|$\)' "$ignorefile" > "$listfile" || true
				LC_CTYPE=C grep -Evf "$listfile"
				;;
			git)
				(git ls-files -oi --exclude-standard; git ls-files -oi --exclude-standard --directory) | sort | uniq > "$listfile" || true
				if [ -s "$listfile" ]; then
					sed 's/^\.\///' | LC_CTYPE=C grep -xFvf "$listfile"
				else
					cat -
				fi
				;;
		esac
		rm -f "$listfile"
		unset listfile
	else
		cat -
	fi
}

shellquote() {
	# Single quotes text, escaping existing single quotes.
	sed -e "s/'/'\"'\"'/g" -e "s/^/'/" -e "s/$/'/"
}

generate_metadata() {
	# This function generates the script commands to fix any file
	# ownerships that aren't owner=root, group=root, as well as to 
	# store the permissions of files.
	# The script is produced on stdout.  Errors go to stderr.
	# 
	# The script can use a 'maybe' function, which only runs a command
	# if the file in its last argument exists.

	# We want files in the directory containing VCS data
	# but we want find to ignore the VCS files themselves.
	# 
	# (Note that when using this, the find expression must end with 
	# -print or -exec, else the excluded directories will actually be
	# printed!)
	NOVCS='. -path ./.git -prune -o -path ./.bzr -prune -o -path ./.hg -prune -o -path ./_darcs -prune -o'

	if [ "$VCS" = git ] || [ "$VCS" = hg ]; then
		# These version control systems do not track directories,
		# so empty directories must be stored specially.
		find $NOVCS -type d -empty -print |
			sort | shellquote | sed -e "s/^/mkdir -p /"
	fi

	if [ "$VCS" = darcs ]; then
		# This version control system does not track symlinks,
		# so they must be stored specially.
		find $NOVCS -type l -print | sort | filter_ignore | while read link; do
			dest=$( readlink "$link" )
			printf "ln -sf '%s' '%s'\n" "$(echo "$dest" | shellquote)" "$(echo "$link" | shellquote)"
		done
	fi
 
	# Store things that don't have the default user or group.
	# Store all file modes, in case the user has an unusual umask.
	find $NOVCS \( -type f -or -type d \) -print | filter_ignore | sort | maybe_chmod_chown

	# We don't handle xattrs.
	# Maybe check for getfattr/setfattr and use them if they're available?
}

maybe_chmod_chown() {
	if command -v perl >/dev/null; then
		/usr/pkg/bin/perl -ne '
			BEGIN { $q=chr(39) }
			sub uidname {
				my $want=shift;
				if (exists $uidcache{$want}) {
					return $uidcache{$want};
				}
				my $name=scalar getpwuid($want);
				return $uidcache{$want}=defined $name ? $name : $want;
			}
			sub gidname {
				my $want=shift;
				if (exists $gidcache{$want}) {
					return $gidcache{$want};
				}
				my $name=scalar getgrgid($want);
				return $gidcache{$want}=defined $name ? $name : $want;
			}
			chomp;
			my @stat=stat($_);
			my $mode = $stat[2];
			my $uid = $stat[4];
			my $gid = $stat[5];
			s/$q/$q"$q"$q/g; # escape single quotes
			s/^/$q/;
			s/$/$q/;
			if ($uid != $>) {
				printf "maybe chown $q%s$q %s\n", uidname($uid), $_;
			}
			if ($gid != $)) {
				printf "maybe chgrp $q%s$q %s\n", gidname($gid), $_;
			}
			printf "maybe chmod %04o %s\n", $mode & 07777, $_;
		'
		return $?
	else
		# fallback if perl isn't present
		euid=$(id -u)
		egid=$(id -g)
		q="'"
		while read x; do
			stat=$(stat -c "%f:%u:%g:%a:%U:%G" "$x")
			IFS=":" read mode uid gid perm uname gname <<EOF
$stat
EOF
			x=$q$(echo $x | sed "s/$q/$q\"$q\"$q/")$q
			if [ $uid -ne $euid ]; then
				echo maybe chown "'$uname'" $x
			fi
			if [ $gid -ne $egid ]; then
				echo maybe chgrp "'$gname'" $x
			fi
			echo maybe chmod 0$perm $x
		done
	fi
}

if [ "$VCS" = git ] || [ "$VCS" = hg ] || [ "$VCS" = bzr ] || [ "$VCS" = darcs ]; then
	if [ -f .metadata ]; then
		# remove obsolete .metadata file
		# git allows fully deleting it at this point, other VCS
		# may not (the repo is locked for hg).
		if [ "$VCS" = git ]; then
			$VCS rm .metadata
		else
			rm -f .metadata
		fi
	fi

	echo "# Generated by etckeeper.  Do not edit." > .etckeeper
	echo >> .etckeeper

	# Make sure the file is not readable by others, since it can leak
	# information about contents of non-readable directories in /etc.
	chmod 700 .etckeeper

	generate_metadata >> .etckeeper

	# stage the file as part of the current commit
	if [ "$VCS" = git ]; then
		# this will do nothing if the metadata file is unchanged.
		git add .etckeeper
	fi
	# hg, bzr and darcs add not done, they will automatically
	# include the file in the current commit
fi
