#!/usr/bin/perl
# pre-commit-access-control-hook.pl
########################################################################

########################################################################
# ACCESS CONTROL HOOK FOR SUBVERSION
#
# Programmed by David Weintraub
# Date: 6-June-2005
# Revision: $Id: pre-commit-access-control-hook.pl 265 2005-08-10 17:06:42Z weintraub $
# Purpose:
#    To control access to files and properties. This program
#    parses a control file that contains the various permission
#    definitions. These can define a "Group" to be used for file
#    permissioning, a property that must be set for a particular
#    file, or a file permission and who is allowed to edit or not
#    edit that file.
#
#    See comentary in control-file.template for more information.
#
########################################################################

########################################################################
# PERL MODULES
#
use Getopt::Long 2.34;
use Config::IniFiles 2.38;
#
########################################################################

########################################################################
# CONSTANTS
#
*DEFAULT_CONTROL_FILE = \"./control-file";
    our $DEFAULT_CONTROL_FILE;
*DEFAULT_DEBUG_LEVEL = \0;
    our $DEFAULT_DEBUG_LEVEL;
#*SVNLOOK_DEFAULT = \"/usr/bin/svnlook";
*SVNLOOK_DEFAULT = \"/usr/bin/svnlook";
    our $SVNLOOK_DEFAULT;

#
#   ####IniFile Groups
#

*GROUP_GROUP = \"group";
    our $GROUP_GROUP;
*FILE_GROUP = \"file";
    our $FILE_GROUP;
*PROP_GROUP = \"Property";
    our $PROP_GROUP;
*REVPROP_GROUP = \"revprop";
    our $REVPROP_GROUP;
*BANNED_GROUP = \"ban";
    our $BANNED_GROUP;

#
#   ####Permission Types for File Read/Write Access
#

*READ_PERM = \"read-only";
    our $READ_PERM;
*WRITE_PERM = \"read-write";
    our $WRITE_PERM;
*ADD_PERM = \"add-only";
    our $ADD_PERM;

#
#    ####Types for "Values" for Properties and Revision Properties
#

*REGEX_TYPE = \"regex";
    our $REGEX_TYPE;
*STRING_TYPE = \"string";
    our $STRING_TYPE;
*NUMBER_TYPE = \"number";
    our $NUMBER_TYPE;

#
#    ####Group Specification Parameters
#

*GROUP_USERS_STR = \"users";
    our $GROUP_USERS_STR;

#
#    ####File "Read/Write" Specifications Parameters
#

*FILE_PATTERN_STR = \"match";
    our $FILE_PATTERN_STR;
*PERM_STR = \"access";
    our $PERM_STR;
*FILE_MEMBER_STR = \"users";
    our $FILE_MEMBER_STR;

#
#    ####Property and Revision Property Permission Specification Parameters
#

*PROP_PATTERN_STR = \"match";
    our $PROP_PATTERN_STR;
*PROP_STR = \"property";
    our $PROP_STR;
*PROP_VAL_STR = \"value";
    our $PROP_VAL_STR;
*PROP_VAL_TYPE_STR = \"type";
    our $PROP_VAL_TYPE_STR;

#
#    ####Banned File Name Specification Parameters
#

*BANNED_PATTERN_STR = \"match";
    our $BANNED_PATTERN_STR;
#
########################################################################

########################################################################
# USAGE
#
our $USAGE = <<EOF;
usage:
    access.pl [-file <ctrlFile>] (-r<revision>|-t<transaction>) \\
        [-debug <debugLevel>] [-svnlook <svnlookCmd>] <repository>
    where:
	<ctrlFile>: Control File used for determining permissions
	    defaults to $DEFAULT_CONTROL_FILE.
	<revision>: Revision Number of archive (for testing)
	<transaction>: Transaction Number of Commit
	<debugLevel>: The debug level to implement>. Default
	    is $DEFAULT_DEBUG_LEVEL (zero means no debugging messages)
	<repository>: Full Path to Repository
	<svnlookCmd>: The svnlook command including the full path to
	              the command.
EOF
#
########################################################################

########################################################################
# PRAGMAS
#
use strict;
use warnings;
#
########################################################################

########################################################################
# OTHER VARIABLES
#
my %groupHash;	#Hash of Lists containing group definitions
my @propList;	#Array of Hash containing property definitions
my @revPropList; #Array of Hash containing rev property definitions
my @fileList;	#Array of Hash containing File Perm definitions
my @bannedFileList; #Array Containing Names of Banned Files
my @sectionList; #Array Containing various Groups of the Ini File
my %sectionHash; #Translates Section Array into Hash lookup
my $svnlookCmd;	#The full path and command of "svnlook"
my $lineNumber = 0;
my $author;	#Name of user who made the changes
my $cfPtr;	#Pointer to Control File's Ini Parameters
my @errorList = ();	#List of parsing error from Control File
#
########################################################################

########################################################################
#  GET COMMAND LINE OPTIONS
#
my $transactionNum;
my $revisionNum;
my $controlFile;
my $option;
my $repository;
my $debugLevel;

GetOptions(
    "t=s" => \$transactionNum,
    "r=i" => \$revisionNum,
    "file=s" => \$controlFile,
    "debug=i" => \$debugLevel,
    "svnlook=s" => \$svnlookCmd
);

if ($revisionNum and $transactionNum) {
    die qq(You cannot specify both a revision and transaction number\n) .
	qq($USAGE\n);
}

unless ($controlFile) {
    $controlFile = $DEFAULT_CONTROL_FILE;
}

unless ($repository = $ARGV[0]) {
    die qq(You must specify a repository\n$USAGE\n);
}

unless ($svnlookCmd) {
    $svnlookCmd = $SVNLOOK_DEFAULT;
}

if ($revisionNum) {
    $option = "-r $revisionNum";
} elsif ($transactionNum) {
    $option = "-t $transactionNum";
} else {
    die qq(You must specify either the "-t" or "-r" option\n$USAGE\n);
}

unless ($debugLevel) {
    $debugLevel = $DEFAULT_DEBUG_LEVEL;
}

########################################################################
# SUBROUTINE DEBUG
#
sub debug {
    if (($debugLevel > 0) && ($_[1] <= $debugLevel)) {
	print "    " x ($_[1] - 1) if ($_[1] >= 1);
	print qq($_[0]\n);
    }
}
#
########################################################################

########################################################################
# SUBROUTINE ADD GROUP
#
# Purpose: To add a list of names to the groupHash (Which is a hash of lists)
#
sub addGroup {
    my $sectionName = $_[0];	#Ini Parameter Section Name
    (my $group = $sectionName) =~ s/^group\s+//;
    if($group =~ /\s/)
    {
	push(@errorList,
	    qq(Ini Section "$group" has a space in group's name!));
    }
    my $members = $cfPtr->val("$sectionName", "$GROUP_USERS_STR");

    $members =~ s/^\s*//;	#Strip Leading Spaces
    $members =~ s/\s*$//;	#Strip Trailing Spaces
    $members =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    unless($members) {
	push (@errorList,
	    qq(Ini Section "$group" has no parameter "$GROUP_USERS_STR" in ) .
	    qq(file "$controlFile"));
	return 1;
    }
    $group =~ s/^@//;		#Remove "@" if on group
    foreach my $member (split(/(\s*(\s|,)\s*)/, $members)) {
	$member =~ s/\s//g;
	$member = lc($member);	#Case is insignificant
	if($member =~ /^@/) {	#This is a group!
	    $member =~ s/^@//;	#Name of Group
	    if (defined($groupHash{$member})) {
		push(@{$groupHash{"$group"}}, @{$groupHash{"$member"}});
	    } else {
		push(@errorList,
		    qq(Line #$lineNumber: Group "$member" undefined));
	    }
	} else {
	    push(@{$groupHash{"$group"}}, "$member");
	}
    }
    return 0;
}
#
########################################################################

########################################################################
# SUBROUTINE ADD PROPERTY
#
sub addProp {
    my $section = $_[0];
    my $filePattern = $cfPtr->val("$section", "$PROP_PATTERN_STR");

    $filePattern =~ s/^\s*//;	#Strip Leading Spaces
    $filePattern =~ s/\s*$//;	#Strip Trailing Spaces
    $filePattern =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    my $property = $cfPtr->val("$section", "$PROP_STR");

    $property =~ s/^\s*//;	#Strip Leading Spaces
    $property =~ s/\s*$//;	#Strip Trailing Spaces
    $property =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    my $propValue = $cfPtr->val("$section", "$PROP_VAL_STR");

    $propValue =~ s/^\s*//;	#Strip Leading Spaces
    $propValue =~ s/\s*$//;	#Strip Trailing Spaces
    $propValue =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    my $valType = $cfPtr->val("$section", "$PROP_VAL_TYPE_STR");

    $valType =~ s/^\s*//;	#Strip Leading Spaces
    $valType =~ s/\s*$//;	#Strip Trailing Spaces
    $valType =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    unless($filePattern) {
	push(@errorList,
	    qq(Missing parameter "$PROP_PATTERN_STR" in section "$section" ) .
	    qq(in IniFile "$controlFile"));
    }
    unless($property) {
	push(@errorList,
	    qq(Missing parameter "$PROP_STR" in section "$section" ) .
	    qq(in IniFile "$controlFile"));
    }
    unless($propValue) {
	push(@errorList,
	    qq(Missing parameter "$PROP_VAL_STR" in section "$section" ) .
	    qq(in IniFile "$controlFile"));
    }
    unless($valType) {
	push(@errorList,
	    qq(Missing  parameter "$PROP_VAL_TYPE_STR" in ) .
	    qq(section "$section" in IniFile "$controlFile"));
    }
    unless($valType =~ /^($REGEX_TYPE|$STRING_TYPE|$NUMBER_TYPE)$/i) {
	push(@errorList,
	    qq(Invalid  parameter "type" = "$valType" in section "$section" ) .
	    qq(in IniFile "$controlFile"));
    }
	my $subscript = $#propList + 1;	#Next free entry in @propList

	$filePattern =~ s/^\^\//\^/;	#Change "^/" to "^" at start of regex
	$propList[$subscript]->{"file"} = $filePattern;
	$propList[$subscript]->{"type"} = $valType;
	$propList[$subscript]->{"property"} = $property;
	$propList[$subscript]->{"value"} = $propValue;

    return 0;
}
#
########################################################################

########################################################################
# SUBROUTINE ADD REVISION PROPERTY
#
sub addRevProp {
    my $section = $_[0];
    my $property = $cfPtr->val("$section", "$PROP_STR");

    $property =~ s/^\s*//;	#Strip Leading Spaces
    $property =~ s/\s*$//;	#Strip Trailing Spaces
    $property =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    my $propValue = $cfPtr->val("$section", "$PROP_VAL_STR");

    $propValue =~ s/^\s*//;	#Strip Leading Spaces
    $propValue =~ s/\s*$//;	#Strip Trailing Spaces
    $propValue =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    my $propType = $cfPtr->val("$section", "$PROP_VAL_TYPE_STR");

    $propType =~ s/^\s*//;	#Strip Leading Spaces
    $propType =~ s/\s*$//;	#Strip Trailing Spaces
    $propType =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes


    my $subscript = $#revPropList + 1;	#Next free entry in @propList

#
#   ####Check Property Type
#
    unless($property) {
	push(@errorList,
	    qq("Missing Parameter "$PROP_STR" in section "$section" ) .
	    qq(in IniFile "$controlFile));
    }
    unless($propValue) {
	push(@errorList,
	    qq("Missing Parameter "$PROP_VAL_STR" in section "$section" ) .
	    qq(in IniFile "$controlFile));
    }
    unless($propType =~ /^($REGEX_TYPE|$STRING_TYPE|$NUMBER_TYPE)$/) {
	push(@errorList,
	    qq("Invalid Parameter "$PROP_VAL_TYPE_STR" value = "$propType" ) .
	    qq(in section "$section" in IniFile "$controlFile));
    }
    unless($propType) {
	push(@errorList,
	    qq("Missing Parameter "type" in section "$section" ) .
	    qq(in IniFile "$controlFile));
    }
    $revPropList[$subscript]->{"property"} = $property;
    $revPropList[$subscript]->{"value"} = $propValue;
    $revPropList[$subscript]->{"type"} = $propType;

    return 0;
}
#
########################################################################

########################################################################
# SUBROUTINE ADD FILE
#
# Purpose: To add a new file permission "record" to a List of Files
#    The record consists of a
#       * File Regex ("pattern")
#       * Permission ("permission") of either "a", "w", or "r"
#       * A hash of users with that permission ("member")
#
sub addFile {
    my $section = $_[0];

    my $filePattern = $cfPtr->val("$section", "$FILE_PATTERN_STR");

    $filePattern =~ s/^\s*//;	#Strip Leading Spaces
    $filePattern =~ s/\s*$//;	#Strip Trailing Spaces
    $filePattern =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    my $permission = $cfPtr->val("$section", "$PERM_STR");

    $permission =~ s/^\s*//;	#Strip Leading Spaces
    $permission =~ s/\s*$//;	#Strip Trailing Spaces
    $permission =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    my $members = $cfPtr->val("$section", "$FILE_MEMBER_STR");

    $members =~ s/^\s*//;	#Strip Leading Spaces
    $members =~ s/\s*$//;	#Strip Trailing Spaces
    $members =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes


    unless($filePattern) {
	push(@errorList, qq(Missing parameter "$FILE_PATTERN_STR" ) .
	    qq(in section "$section" in IniFile "$controlFile));
    }

    unless ($permission =~ /^($READ_PERM|$WRITE_PERM|$ADD_PERM)$/i) {
	push (@errorList, qq(Bad $PERM_STR "$permission" ) .
	    qq(in section "$section" in IniFile "$controlFile"));
    }

    unless($members) {
	push (@errorList, qq(Missing parameter "$FILE_MEMBER_STR" ) .
	    qq(in section "$section in IniFile "$controlFile));
    }


    $filePattern =~ s/^\^\//\^/;	#Change "^/" to "^" at start of regex
    my $subscript = $#fileList + 1; 	#Subscript of fileList Array
    $filePattern =~ s/<USER>/$author/g;
    $fileList[$subscript]->{"$FILE_PATTERN_STR"} = $filePattern;
#
#   ####Check File Permissions
#

    $fileList[$subscript]->{"permission"} = lc($permission);

#
#   ####Parse Group Permissions
#

    my @memberList = ();
    foreach my $member (split(/\s*(,|\s)\s*/, $members)) {
	$member =~ s/\s//g;
	if ($member eq "\@ALL") { #Special Case: Line for All Users 
	    @memberList = ();
	    $memberList[0] = "\@ALL";
	    last;		#All are included. Nothing else to parse
	}
	$member = lc($member);	#Case is insignificant
	if($member =~ /^@/) {	#This is a group!
	    $member =~ s/^@//;	#Name of Group
	    if (defined($groupHash{$member})) {
		push(@memberList, @{$groupHash{"$member"}});
	    } else {
		push(@errorList,
		    qq(Invalid Group \@$member in parameter ) .
		    qq("$FILE_MEMBER_STR" in section "$section" in ) .
		    qq(IniFile "$controlFile));
	    }
	} else {
	    push(@memberList, "$member");
	}
    }

#
#   ####Reformat member group into a hash

    foreach my $member (@memberList) {
	$fileList[$subscript]->{"member"}->{"$member"} = 1;
    }
    return 0;
}
#
########################################################################

########################################################################
# SUBROUTINE ADD BANNED FILE
#
# Purpose: To add a new file name "record" to a List of Files
#    that are not allowed to be committed.
#
#    The record consists of a
#       * File Regex ("pattern")
#
sub addBannedFile {
    my $section = $_[0];
    my $filePattern = $cfPtr->val("$section", "$BANNED_PATTERN_STR");

    $filePattern =~ s/^\s*//;	#Strip Leading Spaces
    $filePattern =~ s/\s*$//;	#Strip Trailing Spaces
    $filePattern =~ s/^"(.*)"$/$1/;	#Strip Containing Quotes

    (my $reason = $section) =~ s/^$BANNED_GROUP\s+//;

    unless ($filePattern)
    {
	push(@errorList, qq(Missing parameter "$FILE_PATTERN_STR" from ) .
	    qq(section "$section" in iniFile "$controlFile"\n));
    }

    my $subscript = $#bannedFileList + 1; 	#Subscript of fileList Array
    $bannedFileList[$subscript]->{"file"} = $filePattern;
    $bannedFileList[$subscript]->{"reason"} = $reason;

    return 0;
}
#
########################################################################

########################################################################
# OPEN THE CONTROL FILE
#
unless (-f "$controlFile") {
    die "Cannot open file \"$controlFile\" for reading\n";
}
    $cfPtr = Config::IniFiles->new(
    -allowcontinue=>1,
    -nocase=>1,
    -file=>"$controlFile"
);

if (not defined($cfPtr))
{
    print STDERR "Error in Parameter file\n";
    my $errors = join ("\n\t\t", @Config::IniFiles::errors);
    print qq(\tErrors are:\n\t\t "$errors\n");
    exit 1;
}

select(STDERR);	#Only STDERR lines print out

#
#   ####Need Author for replacing <USER> in Parameters
#

chomp ($author = qx($svnlookCmd author $option $repository));
#
########################################################################

########################################################################
# ADD GROUPS
#
foreach my $group ($cfPtr->GroupMembers("$GROUP_GROUP")) {
    addGroup("$group");
}
#
########################################################################

########################################################################
# ADD PROPERTIES
#
foreach my $property ($cfPtr->GroupMembers("$PROP_GROUP")) {
    addProp("$property");
}
#
########################################################################

########################################################################
# ADD REVPROPS
#
foreach my $revprop ($cfPtr->GroupMembers("$REVPROP_GROUP")) {
    addRevProp("$revprop");
}
#
########################################################################

########################################################################
# ADD FILES
#
foreach my $file ($cfPtr->GroupMembers("$FILE_GROUP")) {
    addFile("$file");
}
#
########################################################################

########################################################################
# ADD BANNED FILES
#
foreach my $bannedFile ($cfPtr->GroupMembers("$BANNED_GROUP")) {
    addBannedFile("$bannedFile");
}
#
########################################################################

########################################################################
# CHECK FOR ERRORS
#
if (@errorList) {
    print qq(Commit failed due to parsing errors in file "$controlFile"\n);
    for my $error (@errorList) {
	print "    $error\n";
    }
    print "Number of errors: " . scalar(@errorList) . "\n";
    exit 2;
}
#
########################################################################

########################################################################
# GET INFORMATION ABOUT THE FILES CHANGED
#

my $cmd = qq("$svnlookCmd" changed $option "$repository");
open (CHANGED, "$cmd|")
    or die qq(Cannot execute the command "$cmd"\n");

my @changeList = ();
my @checkPropList = ();
my @addList = ();

while (<CHANGED>) {
    chomp;
    /([\S]+)\s+(.*)/;		#Can't use "split". Filename may have spaces
    my $status = $1;
    my $file = $2;

    if ($status =~ /^U/) {
	push (@changeList, $file);	#File Modified (Need "write" permission)
	push (@checkPropList, $file);	#Check Property
    }
    elsif ($status =~ /^A/) {
	push (@addList, "$file");	#File Added (Need "write" or "add" perm)
	push (@checkPropList, $file);	#Check Property
    }
    elsif ($status =~ /^D/) {
	push (@changeList, $file);	#File Modified (Need "write" permission)
    }
    elsif ($status =~ /^.U/)		#File Prop Modified!
    {
	push (@checkPropList, $file);
    }
    else
    {
	die qq(Can't interpret change: "$_"\n);
    }
}
#
########################################################################

########################################################################
# FOR EACH FILE, SEE IF YOU HAVE THE PERMISSION TO CHANGE THE FILE
#
# Flag Status:
#   1: Specified Yes (Allowed and default)
#   0: Specifed No (Not Allowed)
#
my @userRejectList = ();	#List of rejected files

#
#    ####Check for Modifications (User is either not specified or given "w")
#

foreach my $file (@changeList) {
    my $userWriteFlag = 1;		#User Write Permission Not Specified
    foreach my $fileEntry (@fileList) {
	if ($file =~ /$fileEntry->{"$FILE_PATTERN_STR"}/) { #File =~ pattern
	    if (($fileEntry->{"member"}->{"$author"}) or 
		($fileEntry->{"member"}->{"\@ALL"})) {
		if ($fileEntry->{"permission"} =~ /^$WRITE_PERM$/i) {
		    $userWriteFlag = 1;
		}
		else { #File has either "add" or "read" permission on it
		    $userWriteFlag = 0;	#User doesn't have permission
		}	#Checking File Permission
	    }	    #If Permission applies to user
	}	#If pattern matches
    }
    if ($userWriteFlag == 0) {
	push (@userRejectList, qq(No permission to change file "$file"));
    }
}

#
#   ####Check for Additions (User not specified or given "a" or "w")
#

foreach my $file (@addList) {

#
#   ####Checking for Add Permissions for Control-File "file:" Specs
#

    my $userWriteFlag = 1;		#User Write Permission Not Specified
    foreach my $fileEntry (@fileList) {
	if ($file =~ /$fileEntry->{"$FILE_PATTERN_STR"}/) { #File =~ pattern
	    if (($fileEntry->{"member"}->{"$author"}) or 
		($fileEntry->{"member"}->{"\@ALL"})) {
		if (($fileEntry->{"permission"} =~ /^$ADD_PERM$/i) &&
		    ($file =~ /\/$/))	#Only Add Directories!
		{
		    $userWriteFlag = 1;
		}
		elsif ($fileEntry->{"permission"} =~ /^$WRITE_PERM$/i) {
		    $userWriteFlag = 1; #User has write perm
		}
		else {
		    $userWriteFlag = 0;	#User doesn't have permission
		}	#Checking File Permission
	    }	    #If Permission applies to user
	}	#If pattern matches
    }	# For each entry in File Permission List

    if ($userWriteFlag == 0) {
	push (@userRejectList, qq(No permission to add file "$file"));
    }

#
#   ####Checking for Banned Files in Control-File "banned:" Specs
#

    foreach my $fileEntry (@bannedFileList) {
	if ($file =~ /$fileEntry->{"file"}/) { #File =~ pattern
	    push (@userRejectList, qq("$file" can't be added to respostory. ) .
		qq(Banned!\n\t $fileEntry->{reason}));
	}
    }
}
#
########################################################################

########################################################################
# CHECK PROPERTIES
#
foreach my $file (@checkPropList) {
    foreach my $fileEntry(@propList) {
	if ($file =~ /$fileEntry->{"file"}/) { #Check Property
	    my $property = $fileEntry->{"property"};
	    my $cmd = qq("$svnlookCmd" propget $option ) .
		qq("$repository" "$fileEntry->{property}" "$file" 2> /dev/null);
	    my $tempVal = qx($cmd);
	    unless (my $propValue = qx($cmd)) {
		push(@userRejectList,
		    qq(Property "$property" needed for file "$file".\n) .
		    qq(        Must match $fileEntry->{type} ) .
		    qq('$fileEntry->{value}'.));
	    } else {		#Property Found: Is it a good value?
		chomp ($propValue);
		if ($fileEntry->{"type"} eq "$REGEX_TYPE") {
		    if ($propValue !~ /$fileEntry->{"value"}/) {
			push (@userRejectList, 
			    qq(Property "$property" for file "$file" ) .
			    qq(is invalid.\n) .
			    qq(        Must match regex ) .
			    qq(/$fileEntry->{value}/.));
		    }
		}
		elsif ($fileEntry->{"type"} eq "$STRING_TYPE") {
		    if ($propValue ne $fileEntry->{"value"}) {
			push (@userRejectList, 
			    qq(Property "$property" for file "$file" ) .
			    qq( is invalid.\n) .
			    qq(        Must match string ) .
			    qq("$fileEntry->{value}".));
		    }
		}
		else {	#Assume it is a number
		    if ($propValue != $fileEntry->{"value"}) {
			push (@userRejectList, 
			    qq(Property "$property" for file "$file" ) .
			    qq(is invalid.\n) .
			    qq(        Must match number ) .
			    qq($fileEntry->{value}.));
		    }
		}
	    }
	}	#If @propList has this file for this property
    } #For each file entry found in @propList
}
#
########################################################################

########################################################################
# CHECK REVISION PROPERTIES
#
foreach my $propEntry (@revPropList) {
    my $cmd;
    my $property = $propEntry->{"property"};


#
#   ####Kludge: This is a kludge. The "-t" flag really doesn't work on
#       the "svn propget --revprop" command. However, it would be nice
#       to have a way to check the log message anyway, so I will simply
#       kludge the "svnlook propget --revprop svn:log" to "svnlook log".
#

    if (("$property" eq "svn:log") and ($option =~ /^-t/))
    {
	$cmd = qq("$svnlookCmd" log $option "$repository" 2> /dev/null);
    }
    else
    {
	$cmd = qq($svnlookCmd propget --revprop $option ) . 
	    qq("$repository" "$property" #2> /dev/null);
    }
    unless(my $propValue = qx($cmd)) {
	push (@userRejectList,
	qq(Revision Property "$property" must be set!\n) .
	    qq(        Must be a $propEntry->{type} set to ) .
	    qq("$propEntry->{value}".));
    } else {	#Revision Property does exist!
	chomp ($propValue);
	if ($propEntry->{"type"} eq "$REGEX_TYPE") {
	    if ($propValue !~ /$propEntry->{"value"}/) {
		push (@userRejectList,
		    qq(Revision Property "$property" is invalid.\n) .
		    qq(        Currently set to "$propValue".\n) .
		    qq(        Must match regex /$propEntry->{"value"}/.));
	    }
	}
	elsif ($propEntry->{"type"} eq "$STRING_TYPE") {
	    if ($propValue ne "$propEntry->{value}") {
		push (@userRejectList,
		    qq(Revision Property "$property" is invalid.\n) .
		    qq(        Currently set to "$propValue".\n) .
		    qq(        Must match string "$propEntry->{"value"}".));
	    }
	}
	else {	#Assume it's a number
	    if ($propValue != $propEntry->{"value"}) {
		push (@userRejectList,
		    qq(Revision Property "$property" is invalid.\n) .
		    qq(        Currently set to "$propValue".\n) .
		    qq(        Must match number "$propEntry->{"value"}".));
	    }
	}
    }
}
#
########################################################################

########################################################################
# CHECK USER REJECT LIST
#
if(scalar @userRejectList) {
    print "ERROR: Commit failed for the following reasons:\n";
    foreach my $entry (@userRejectList) {
	print "    $entry\n";
    }
    exit 2;
} else {	#Commit is fine
    exit 0;
}
#
########################################################################
