#!/usr/bin/perl -w
#
# $FreeBSD$
#

# Perl filter to handle cvs editinfo preparation of log messge

# This filter should be named in the $CVSROOT/CVSROOT/editinfo file
# for any modules you wish to add the gnats bug system bugid and
# header.
# A line like "DEFAULT		/path/to/this/perl/script"
# will do the trick.

# Some tracking information may be logged
$debugging = 0;			# used in sub debug   for tracking
$logging   = 0;			# used in sub log     for tracking

$debugging = 1 if (defined $ENV{'CVSDEBUGEDIT'});

#########################################################################
#
# derrived information needed below
#
#########################################################################

$login = (getpwuid($<))[0] || "Anonymous"; # get user's login name


#########################################################################
#
# Paths to utilities
#
#########################################################################

$defaultEditorCmd  = '/usr/bin/vi'; # use env $EDITOR to override
$editorCmd         = ((defined $ENV{'EDITOR'}) ?
                      $ENV{'EDITOR'} :
		      ((defined $ENV{'EDITOR'}) ?
		       $ENV{'EDITOR'} : $defaultEditorCmd));


#########################################################################
#
# Constants
#
#########################################################################
$BadTmpDir = "
It is a very bad idea for the environment variable TMPDIR
currently set to \'$ENV{'TMPDIR'}\' to be a relative pathname.
Please choose something with a leading slash (/tmp) for example
\n";

$MissingTmpDir = "
It is a very bad idea for the environment variable TMPDIR
currently set to \'$ENV{'TMPDIR'}\' to be directory which does not
exist. Please create this directory and give it read-write permissions
to group cisco.
\n";


#########################################################################
#
# Inter-filter communication file names
#
#########################################################################

# Do not change these constants without changing them in the other
# scripts as well.

$pgrp		   = getpgrp();		# parent process id

if (defined $ENV{'TMPDIR'}) {
    # trivial sanity check, are we able to write into this directory?
    if ($ENV{'TMPDIR'} !~ /^\//) {
	&debug("Bad... TMPDIR is not a full pathname");
	&abort($BadTmpDir);
    }
    elsif ( -d $ENV{'TMPDIR'} && -w _ ) {
	&debug("Good.. TMPDIR references a directory we can use");
	$tmpDir = $ENV{'TMPDIR'};
    }
    else {
	&debug("Bad... TMPDIR is not a directory in which we can write files");
	&abort($MissingTmpDir);
    }
}
else {
    $tmpDir = '/tmp';
    if ( -d $tmpDir && -w _ ) {
	&debug("Good.. $tmpDir looks like we can use it");
    }
    else {
	&debug("Bad... $tmpDir is not a directory in which we can write files");
	&abort("This system is in real trouble $tmpDir is not writable");
    }
}

$gnatsReportFile   = $tmpDir . '/' . $login . '.cvsgnats'    . $pgrp;
$logFile	   = $tmpDir . '/' . $login . '.cvslog'      . $pgrp;

#########################################################################
#
# main
#
#########################################################################

if (! @ARGV) {
    &abort( "No message filename was given. Usage: $0 cvs-log-file\n" );
}

$userLogMessageFile = $ARGV[0];

&create_timestamps();
$hostname = `hostname`;
chop($hostname);

if ($logging) {
    if (!open(LOGFILE, ">> $logFile")) {
	warn "Cannot open log file: $logFile\nturning logging off";
	$logging = 0;
    }

    &log("Start: sys_editinfo attempt by $login at $date");
    &log("Command: $0" . join(' ',@ARGV));
}

&debug("Command: $0 \'" . join('\' \'',@ARGV).'\'');

# $gnatsReportFile will already have built by commitinfo
if ( -e $gnatsReportFile ) {
    &debug("using existing gnats report: $gnatsReportFile");

    # Get bug Id from gnats report
    $bugId = '';
    $bugBranch = '';
    ($bugId, $bugBranch, $headline) =
	&getBugId_from_gnatsreport( $gnatsReportFile );
    $bugBranch =~ s/^-//;	# remove leading dash

    &log("Bug Id $bugId extracted from $gnatsReportFile");
    &log("Headline $headline extracted from $gnatsReportFile");
    &log("Branch \'$bugBranch\' extracted from $gnatsReportFile");

    # If we have a valid bug id, complete bug file names
    if ( $bugId eq '' ) {
	warn "Bug Id not found in GNATS report $gnatsReportFile";
    }
}

@loglines = &read_file( $userLogMessageFile );
if (($bugId ne '') && ($bugId ne 'sync') && ($bugId ne 'new') &&
    ($bugId ne 'placeholder')) {
    if (! grep(/$bugId/, @loglines)) {
	&debug("adding bugid and headline to $userLogMessageFile");
	&debug(">>>$bugId: $headline");
	push(@loglines, $bugId.': '.$headline);
	&write_file( $userLogMessageFile, @loglines );
    }
}
if ($bugBranch ne '') {
    if (! grep(/^Branch:/, @loglines)) {
	&debug("adding Branch information to $userLogMessageFile");
	&debug(">>>Branch: $bugBranch");
	push(@loglines, 'Branch: '.$bugBranch);
	&write_file( $userLogMessageFile, @loglines );
    }
}

$doedit = 1;
while ($doedit) {
    $doedit = 0;
    $diff = 0;

    while (&editFile( $editorCmd, $userLogMessageFile )) {
	print "A non-zero return code usually means that either something\n";
	print "went wrong with your edit session, or you were using vi\n";
	print "and executed a bad command. You may wish to edit $userLogMessageFile\n";
	print "from some other window before answering this next query.\n";
	print "a \'y\' will try the edit command once more and a \'n\'\n";
	print "will fall through and proces the log message at once\n";
	print "Do you wish me to run the command\n";
	$ans = &yes_no("$editorCmd $userLogMessageFile ?", 'n');
	last if ($ans eq 'n');
    }

    @statlines = grep(/^Reviewed by:\s*\S+|^Submitted by:\s*\S+|Obtained from:\s*\S+/, &read_file ( $userLogMessageFile ));

    @loglines = grep(!/^CVS:|^Reviewed by:|^Submitted by:|^Obtained from:/, &read_file( $userLogMessageFile ));

    $orig = join("\n", @loglines);
    $maxlen = 0;
    foreach (@loglines) {
	$save = $_;
	s/[ \t\n]+$//;		# delete trailing space
	1 while s/\t+/' 'x (length($&) * 8 - length($`) % 8)/e; # expand tabs
	s,/\*,/ *,g;		# defuse C Comment start
	s,\*/,* /,g;		# defuse C Comment end
	$diff = 1 if ($save ne $_);
	$maxlen = length($_) if (length($_) > $maxlen);
    }

    if ($maxlen > 72) {
	&write_file( $userLogMessageFile, @loglines );
	open(FMT, "fmt -s $userLogMessageFile |")
	    || die "unable to use fmt: $!";
	@loglines = <FMT>;
	close(FMT);

	foreach (@loglines) {
	    chop;
	    1 while s/\t+/' 'x (length($&) * 8 - length($`) % 8)/e; # expand tabs
	}
    }

    $leading = 200;
    foreach (@loglines) {
	next if /^$/;
	# notice leading whitespace
	($tmp = $_) =~ s/^(\s*).*$/$1/;
	$leading = length($tmp) if (length($tmp) < $leading);
    }

    # nuke $leading blanks
    if ($leading > 0) {
	&debug("removing $leading leading spaces");
	$tmp = ' ' x $leading;
	foreach (@loglines) {
	    s/^$tmp//;
	}
    }
    
    $new = join("\n",@loglines);
    if ($orig ne $new) {
	@loglines = ( @statlines, @loglines );
	&write_file( $userLogMessageFile, @loglines );

	print STDERR "=============== New log text ===============\n";
	print STDERR join("\n", @loglines);
	print STDERR "\n=============== end log text ===============\n\n";
	$ans = &yes_no("Formated log differs from original text. Use it?", 'y');
	exit(0) if ($ans eq 'y');
	$ans = &yes_no("Do you wish to edit the text?", 'y');
	if ($ans eq 'n') {
	    $ans = &yes_no("Ok to abort?", 'n');
	    &abort("terminated by user request") if ($ans eq 'y');
	}
	$doedit = 1;
    }
}

@loglines = &read_file( $userLogMessageFile );
if (($bugId ne '') && ($bugId ne 'sync') && ($bugId ne 'new') &&
    ($bugId ne 'placeholder')) {
    if (! grep(/$bugId/, @loglines)) {
	print STDERR "\n  Please note that removing $bugId\n";
	print STDERR "  from the log message is not good.\n\n";
    }
}

close(LOGFILE) if ($logging);

exit(0);


#########################################################################
#
# Subroutines
#
#########################################################################


sub abort {
    local( $abortMsg ) = @_;
    die ( "[ERROR] ", "$abortMsg\n" );
}

sub create_timestamps {
    ($sec,$min,$hour,$mday,$mon,$year) = localtime;
    $today	= sprintf("%d/%02d/%02d", $year+1900, $mon+1, $mday);
    $nowtime	= sprintf("%02d:%02d:%02d", $hour, $min, $sec);
    $date	= $today . ' ' . $nowtime;
}

sub debug {
    print @_, "\n" if ( $debugging );
}

sub editFile {
    local( $editor, $filename ) = @_;
    local( $return_code );

    &debug("editing file: $filename");
    print "invoking editor...\n$editor $filename\n";
    $return_code = system("$editor $filename");
    $return_code = $return_code / 256;
    if ($return_code) {
	print "$editor returned with an error code of $return_code\n";
    }
    $return_code;
}

sub log {
    print LOGFILE @_, "\n" if ($logging);
}

sub read_file {
    local($filename) = @_;
    local(@text);

    &debug("read file $filename");
    open(FILE, "<$filename") || return ();
    while (<FILE>) {
	chop;
	push(@text, $_);
    }
    close(FILE);
    @text;
}

sub write_file {
    local($filename, @lines) = @_;

    &debug("Writing file $filename");
    open(FILE, ">$filename") || die ("Cannot open log file $filename.\n");
    foreach (@lines) {
	print FILE $_, "\n";
    }
    close(FILE);
}

sub getBugId_from_gnatsreport {
    local( $diffsFile ) = @_;
    local( $bug_id, $branch, $headline );

    &debug( "Opening file $diffsFile to get Bug Id");
    open ( GNATSREPORT, $diffsFile ) ||
	&abort( "Can't open gnats report $diffsFile" );

    $bug_id = 'NULL';
    $branch = 'NULL';
    $headline = '';
    # Find the Bug Id
    while ( <GNATSREPORT> ) {
	chop;
	if ( /^Start: / ) {
	    ($bug_id = $_) =~ s/[^:]*: //;
	    &debug( "Bug Id = $bug_id" );
	    $headline = 'syncronization commit' if ($bug_id eq 'sync');
	}
	elsif ( /^Headline:/) {
	    ($headline = $_) =~ s/[^:]*://;
	    &debug( "Headline = $headline" );
	}
	elsif ( /^Branch: / ) {
	    ($branch = $_) =~ s/[^:]*: //;
	    if ( $branch eq '' ) {
		&debug( "Branch = \'\' (main trunk)" );
	    }
	    else {
		&debug( "Branch = $branch" );
	    }
	}
	last if (($bug_id ne 'NULL') && ($branch ne 'NULL') &&
		 ($headline ne ''));
    }
    close ( GNATSREPORT );
    return ($bug_id, $branch, $headline);
}

# yes_no - ask the user the specified question - repeatedly, until
#	   they answer with a valid answer (i.e. yes or no).  Returns
#	   "n" or "y".
sub yes_no {
    local($ques, $default) = @_;

    for (;;) {
	print "$ques [$default] ";
	$_ = <STDIN>;
	chop;
	/^[yY]$/ && return "y";
	/^[nN]$/ && return "n";
	return $default if (!$_ && $default);
    }
}