#!/usr/bin/perl 
##-----------------------------------------------------------------------------
## pre-commit-check.pl 
##-----------------------------------------------------------------------------
## This is a Subversion pre-commit hook script that can do the following: 
##  * Check the log message length
##  * Only allow modification of TAGS by admin user
##  * Validate svn:externals use the -r parameter
## As required by Subversion, if all tests pass, this script exits with a 
## return code of 0 indicating success. If there is an error, the error message
## is printed on STDERR, and the script exits with a non 0 return code. 
##-----------------------------------------------------------------------------
## USAGE:
##    pre-commit-check.pl REPO_PATH TX_NUM {-d}
##        REPO_PATH = Path to local repository
##        TX_NUM    = Pending transaction number (or revision number if
##                    debugging is enabled
##         -d       = Enable debugging
##
##-----------------------------------------------------------------------------
use strict; 

##
## Modify this to match the path to svnlook
##
my $svnlook = "/usr/bin/svnlook";


## Indicates the minimum log message length 
## If set to 0, message length will not be checked 
my $logMessageLengthMinimum = 10; 



## Indicates if the user can modify tags
## Any non 0 value inidcates a "normal" user can modify tags
my $allowTagModify = 0; 

## Indicates the username that can modify tags
## Only used if $allowTagModify is set to 0 
my $svnSuperUser = "Admin"; 

## Indicates if svn:externals should be validated to ensure
## a -r parameter is provided
## Any non 0 value indicates this test should be performed
my $svnExternalsCheck = 0;

##!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
##!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
## No more modifications should be needed below this point
##!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
##!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

## Misc. variables 
my $repo_path = $ARGV[ 0 ]; 
my $txn_name = $ARGV[ 1 ]; 
my $logMessageLength = 0; 
my $debugEnabled = 0; 
my $argNum = 0; 
my $argC = 0;
my $exitCode = 0;

##-----------------------------------------------------------------------------
## 
## Parse the command line 
## 
##-----------------------------------------------------------------------------
{
  foreach $argNum (0 .. $#ARGV) 
  { 
    if ( ($ARGV[$argNum] eq "-d") || ($ARGV[$argNum] eq "-D")) 
    { 
      $debugEnabled++; 
    } 
    $argC++;
  } 
  
  ## Validate arguments 
  if ((($debugEnabled) && ($argC < 3)) || ((!$debugEnabled) && ($argC < 2)))
  {
    printf STDERR "ERROR: Incorrect number of arguments!\n\n";
    exit 1;
  }

  ## Display debug banner
  if ($debugEnabled) 
  { 
    print "\n------ DEBUG LEVEL $debugEnabled ------\n"; 
    ## Use revision numbers since there is not a transaction number 
    $txn_name = "-r $txn_name"; 
  } 
  else 
  { 
    ## Use transaction numbers 
    $txn_name = "-t $txn_name"; 
  } 
}


##-----------------------------------------------------------------------------
## Check to see if svn:externals have the -r parameter 
##-----------------------------------------------------------------------------
{
  if ($svnExternalsCheck)
  {
    my @directories;
    my $dir;
    my $prop_val;
    if ($debugEnabled >= 2)
    {
      print "\"$svnlook\" dirs-changed $txn_name \"$repo_path\"\n"
    }
    open( INPUT, "\"$svnlook\" dirs-changed $txn_name \"$repo_path\"|" ); 
    while( <INPUT> ) 
    {
      chomp;
      if ($debugEnabled >= 2)
      {
        print "CHANGED: \"$_\"\n";
      }
      push @directories, $_;
    } 
    close INPUT;
    
    my @externalsFound;
    foreach $dir ( @directories )
    {
      if ($debugEnabled >= 2)
      {
        print "Checking proplist of \"$dir\"\n";
        print "\"$svnlook\" proplist $txn_name \"$repo_path\" \"$dir\"\n"
      }
      open( INPUT, "\"$svnlook\" proplist $txn_name \"$repo_path\" \"$dir\"|" ); 
      while( $prop_val = <INPUT> ) 
      {
        chomp $prop_val;
        if ( $prop_val =~ /svn:externals/i )
        {
          push @externalsFound, $dir;
          if ($debugEnabled >= 2)
          {
            print "WILL";
          }
        }
        elsif ($debugEnabled >= 2)
        {
          print " WILL NOT";
        }
        if ($debugEnabled >= 2)
        {
          print " check $dir\n";
        }
      }
      close INPUT;
    }
    
    my $externalsPassed = 1;
    
    foreach $dir ( @externalsFound )
    {
      print "Checking externals of \"$dir\"\n";
      open( INPUT, "\"$svnlook\" propget $txn_name \"$repo_path\" svn:externals \"$dir\"|" );
      while( $prop_val = <INPUT> ) 
      {
        chomp $prop_val;
        if ( $prop_val =~ /^(\".*\"|\S*)\s+\-r\s*(\d*)\s+(.*)/ )
        {
          if ($debugEnabled >= 2)
          {
            print "\"$prop_val\"\n";
            print "OK: $1 is rev $2 of $3\n";
          }
        }
        else
        {
          $externalsPassed = 0;
          printf STDERR "ERROR: svn:externals is missing the -r parameter\n";
          printf STDERR "      \"$prop_val\$\n";
        }
      }
      close INPUT;
    }
    
    if (!$externalsPassed)
    {
      $exitCode++;
    }
  }
  elsif ($debugEnabled)
  {
    print "Skipping svn:externals validation\n"
  }
}  
  
##-----------------------------------------------------------------------------
## Check to see if the log message is long enough 
##-----------------------------------------------------------------------------
{
  if ($logMessageLengthMinimum > 0) 
  { 
    open( INPUT, "\"$svnlook\" log $txn_name \"$repo_path\"|" ); 
    while( <INPUT> ) 
    { 
      $logMessageLength += length; 
    } 
    close INPUT; 
     
    if ($logMessageLength < $logMessageLengthMinimum) 
    { 
      printf STDERR "ERROR: Log message length ($logMessageLength) does not "; 
      printf STDERR "meet minimum length requirements of ";
      printf STDERR "$logMessageLengthMinimum.\n";
      $exitCode++;
    } 
  }
}

##-----------------------------------------------------------------------------
## Check to see TAGS are being modified 
##-----------------------------------------------------------------------------
sub ValidateTagsModification()
{
  ## Check to see if this is a super user that is allowed to change TAGS 
  if (!$allowTagModify)
  {
    open( INPUT, "\"$svnlook\" author $txn_name \"$repo_path\"|" ); 
    while( <INPUT> ) 
    { 
      if( /^$svnSuperUser/i ) 
      { 
        if ($debugEnabled) 
        { 
          print "Author == \"$svnSuperUser\", will not check for tags 
    modification\n" 
        } 
        $allowTagModify = 1; 
      } 
    } 
    close INPUT; 
  }
  elsif ($debugEnabled)
  {
    print "Skipping TAGS modification tests\n"
  }
  
  
  ## Check to see if someone is modifying a TAGS directory 
  if (!$allowTagModify) 
  { 
    if ($debugEnabled) 
    { 
      print "Checking to see if TAGS directory is being modified\n"; 
    } 
     
    open( INPUT, "\"$svnlook\" changed $txn_name \"$repo_path\"|" ); 
    while( <INPUT> ) 
    { 
      if( /^[UD]\s*?.*?\/?tags\/.*/i ) 
      { 
        printf STDERR "Tags can only be modified by $svnSuperUser.\n";
        $exitCode++;
        last;
      } 
    }
    close INPUT;
  }
}

##-----------------------------------------------------------------------------

if ($debugEnabled)
{
  print "\n\nExit code = $exitCode\n\n";
}

exit $exitCode;
##-----------------------------------------------------------------------------

