#!/bin/ksh
# Adjust SVN ignore lists
# Author: Perette Barella
# Copyright 2020 Devious Fish.  All rights reserved.
VERSION='$Id: svnignore 97 2023-11-08 20:19:01Z perette $'

arg0=$(basename "$0")

# Validate that ksh supports the modern/extended getopts format.
# Author: Perette Barella 
# Copyright 2018 Devious Fish.  All rights reserved.
# $Id: modern_ksh_check 19 2018-07-28 23:40:39Z perette $  


function modern_ksh_check {
	if [[ $(getopts '[-][12:abc]' flag --abc; print -- 0$flag) != "012" ]]
	then
		print -- "$arg0${arg0+: }Outdated Korn shell." 1>&2
		exit 1
	fi
}

modern_ksh_check

USAGE=$'
[-1?'$VERSION$']
[+NAME?'$arg0$' - view, add, remove, or edit repository \vsvn:ignore\v property]
[+DESCRIPTION?\b'$arg0$'\b adds or removes files from the \vsvn:ignore\v
property of a Subversion repository.  If no files are listed and no
other action specified, \b'$arg0$'\b displays the current contents of
\vsvn:ignore\v.]
[+?Subversion allows patterns in the ignore list.  \b'$arg0$'\b will insert
and remove these patterns, but they must be quoted to prevent expansion by the
shell.]
[a:add?Add entries to the \vsvn:ignore\v list.]
[e:edit?Edit \vsvn:ignore\v list.]
[l:list?List files being ignored.  If files are provided, evalutes those;
otherwise, evalutes contents of the directory.]
[r:recursive?Work recursively.]
[u:unignore?Remove instead of adding entries.]
[v:verbose?Output additional status messages.]
[100:revert?Revert ignored file status to HEAD.]
[+EXIT STATUS?0 on success, non-0 on error.]
[+SEE ALSO?\bsvn\b(1)]

files ...

[-author?Perette Barella <perette@deviousfish.com>]
'


# Make a temp file in the current directory
function make_temp_here {
        mktemp "$@" "$PWD/$arg0.$$XXXXXX"
}

# Run a command against 'svn', and collect it error reason code.
# Return: 0 and no output on success.
# On error or warning, returns non-0 and writes error or warning code on stdout.
function svn_try {
	typeset message error
	message=$(svn "$@" 2>&1) && return 0
	message=$(print -- "$message" | head -1)
	error=$(print -- "$message" | cut -d: -f2)
	error=${error#[ \t]}
	if [[ $error == E[0-9]* ]]
	then
		print -- "$error"
		return 1
	fi
	if [[ "$error" != "warning" ]]
	then
		print "$arg0: svn: Expected error code, got: $error." 1>&2
		return 1
	fi
	error=$(print -- "$message" | cut -d: -f3)
	error=${error#[ \t]}
	if [[ $error == W[0-9]* ]]
	then
		print -- "$error"
		return 1
	fi
	print "$arg0: svn: Expected warning code, got: $error." 1>&2
	return 1
}


# Check if a name matches an ignore pattern.
# Return: 0 if ignored, 1 if not.
function pattern_is_ignored {
	typeset filename="$1" patternfile="$2" pattern
	# If the file is in the repository, it cannot be ignored
	while read pattern
	do
		[[ $filename == $pattern ]] && return 0
	done < "$patternfile"
	return 1
}


# Show the current ignored files/patterns.
function perform_read {
	if svn propget svn:ignore . 2>/dev/null
	then
		return 0
	elif $VERBOSE
	then
		print "No files ignored." 1>&2
		print 1>&2
	fi
	return 0
}

# Add some patterns from the ignore list.
function perform_add {
	typeset TEMP=$(make_temp_here)
	trap "rm -f '$TEMP'" exit

	svn propget svn:ignore . > "$TEMP" 2>/dev/null
	typeset pattern changed=false
	for pattern in "$@"
	do
		if [[ $pattern == */* ]]
		then
			(cd $(dirname "$pattern") && perform_add $(basename "$pattern")) || return $?
		elif ! fgrep -x -q "$pattern" "$TEMP"
		then
			print -r -- "$pattern" >> "$TEMP"
			changed=true
		else
			$VERBOSE && print -r -- "$pattern: Already present." 1>&2
		fi
	done
	if $changed
	then
		svn propset svn:ignore "$(<$TEMP)" .
		return $?
	fi
	return 0
}


# Remove some patterns from the ignore list.
function perform_remove {
	typeset TEMP=$(make_temp_here)
	typeset TEMP2=$(make_temp_here)
	typeset TEMP3=$(make_temp_here)
	trap "rm -f '$TEMP' '$TEMP2' '$TEMP3'" exit

	svn propget svn:ignore . > "$TEMP" 2>/dev/null
	typeset pattern changed=false
	for pattern in "$@"
	do
		if [[ $pattern == */* ]]
		then
			(cd $(dirname "$pattern") && perform_remove $(basename "$pattern")) || return $?
		else
			print -r -- "$pattern" >> "$TEMP2"
		fi
	done
	# Remove blank lines while we're at it
	print >> "$TEMP2"
	fgrep -v -x -f "$TEMP2" "$TEMP" > "$TEMP3"
	if ! cmp -s "$TEMP" "$TEMP3"
	then
		svn propset svn:ignore "$(<$TEMP3)" .
		return $?
	fi
	return 0
}

# Check a list of files to see if they match an ignore pattern.
# If no files are provided, compare everything in the current directory.
function perform_list {
	if (( $# == 0 ))
	then
		perform_list *
	else
		typeset patterns=$(make_temp_here) file
		trap "rm -f '$patterns'" exit

		svn propget svn:ignore . > "$patterns" 2>/dev/null

		for file in "$@"
		do
			if svn_try info "$file" >/dev/null
			then
				$VERBOSE && print -r -- "$file: In repository"
			elif pattern_is_ignored "$file" "$patterns"
			then
				$VERBOSE && print -r -- "$file: Ignored"
				$VERBOSE || print -- "$file"
			else
				$VERBOSE && print -r -- "$file: Not ignored"
			fi
		done
	fi
	return $?
}


# Open the ignore list in an editor.
function perform_edit {
	if (( $# > 0 ))
	then
		print "$arg0: Arguments ignored for editing." 1>&2
	fi
	svn propedit svn:ignore .
	return $?
}


# Revert the ignore list.
function perform_revert {
	if (( $# > 0 ))
	then
		print "$arg0: Arguments not allowed.  No action taken." 1>&2
		exit 1
	fi
	typeset ignores version
	version=$(svnversion) || version="HEAD"
	if ignores=$(svn propget svn:ignore . -r "$version" 2>/dev/null)
	then
		svn propset svn:ignore "$ignores" .
	else
		svn propdel svn:ignore .
	fi
	return $?
}



MODE=add
RECURSIVE=false
VERBOSE=false

while getopts -a "$arg0" "$USAGE" option
do
	case "$option" in
	    a)
		MODE=add
		;;
	    e)
		MODE=edit
		;;
	    l)
		MODE=list
		;;
	    r)
		RECURSIVE=true
		;;
	    u)
		MODE=remove
		;;
	    v)
		VERBOSE=true
		;;
	    100)
		MODE=revert
		;;
	esac
done
shift $((OPTIND - 1))

if [[ $MODE == add ]] && (( $# == 0 ))
then
	MODE=read
fi

result=0
if $RECURSIVE
then
	here="$PWD"
	find . -type d -print | while read -r directory
	do
		directories[${#directories[@]}]="${directory#./}"
	done
	for directory in "${directories[@]}"
	do
		if cd "$here/$directory"
		then
			$VERBOSE && print "$arg0: Entering $directory/" 1>&2
			if errcode=$(svn_try propget svn:ignore .) ||
			   [[ $errcode == W200017 ]]
			then
				eval "perform_$MODE \"\$@\""
			else
				print "$directory: Not under SVN control." 2>&1
				result=1
			fi
		fi
	done
else
	eval "perform_$MODE \"\$@\"" || result=$?
fi

exit $result

