#!/usr/bin/perl ## speccomm.pl - form trigger for saving comments, whitespace, and user. ## ## ALL WARRANTIES ARE HEREBY DISCLAIMED. ## ## Usage: ## NAME form-in TYPE "speccomm.pl in %user% %formfile% %formtype% %formname%" ## NAME form-out TYPE "speccomm.pl out %user% %formfile% %formtype% %formname%" ## NAME form-commit TYPE "speccomm.pl commit %user% %formfile% %formtype% %formname%" ## ## TYPE can be just about any form, although jobs and changelists might not work too ## well since they can get assigned names only after they've been committed. ## Configuration section: you must set up a client workspace on the server, ## and set $root to the workspace directory that spec files will go into. ## Paths with spaces in them probably won't work. Sorry. ## ## Set up the client view however you like. ## Remember to lock it, since changing it will break the trigger. ## Provide the client name and other connection info in $p4. $p4 = "p4 -p 1981 -c spec-comments -u myuser -P mypasswd"; $root = "c:\\test\\local\\server\\spec"; ## end configuration section use Cwd qw(chdir); # to make sure PWD is set on Unix when chdir'ing use File::Compare; use File::Copy; $log = ""; $_ = shift @ARGV; chdir $root or &warn( "Can't cd to $root: $!" ); if ( $_ eq "in" ) { &in ( @ARGV ); } elsif ( $_ eq "out" ) { &out ( @ARGV ); } elsif ( $_ eq "commit" ) { &commit( @ARGV ); } else { &warn( "Invalid trigger usage." ); } sub in { my ( $user, $formfile, $formtype, $formname ) = @_; if ( !$formname ) { $formname = &getname( $formfile, $formtype ); } if ( !$formname ) { $formname = "form"; } $type = ucfirst($formtype); # Save incoming form to workspace. mkdir $formtype; chdir $formtype; &run( "$p4 revert $formname" ); &run( "$p4 sync $formname" ); &run( "$p4 edit $formname" ); $save = ( (-e $formname) && !&diff($formfile,$formname) && compare($formfile,$formname) ); copy( $formfile, $formname ) or &warn( "copy $formfile $formname failed: $!" ); if ( $save ) { &run( "$p4 submit -d \"$user updated $formtype $formname.\" $formname" ); } } sub out { my ( $user, $formfile, $formtype, $formname ) = @_; if ( !$formname ) { $formname = "form"; } # Get saved form, make sure only differences are comments. &run( "$p4 print -q -o form $formtype/$formname" ); exit unless -e "form"; chmod 0666, "form" or &warn( "chmod form failed: $!" ); &refresh( $formfile, "form" ); if ( diff( "form", $formfile ) ) { unlink( "form" ); &warn("No comments found."); } # Replace outgoing form with saved form. copy( "form", $formfile ); unlink( "form" ); } sub commit { my ( $user, $formfile, $formtype, $formname ) = @_; if ( !$formname ) { $formname = "form"; } # Make sure workspace matches committed form. chdir $formtype or &warn( "Can't cd to $formtype: $!" ); &refresh( $formfile, $formname ); &warn( "Discarding comments." ) if diff( $formfile, $formname ); # Check in saved form by any means necessary. &run( "$p4 add $formname" ); &run( "$p4 submit -d \"$user updated $formtype $formname.\" $formname" ); } # Comment-and-whitespace-insensitive diff for two Perforce forms. # Used to figure out whether the saved form w/ comments we have is # a match for the actual form in the db. sub diff { my ( $f1, $f2 ) = @_; my @form1 = (); my @form2 = (); open FORM1, $f1 or &warn( "read: $f1: $!" ); while ( ) { s/\#.*//; s/\s+/ /g; next if /^Update:/; next if /^Access:/; next if !/[^\s]/; push @form1, $_; } close FORM1; open FORM2, $f2 or &warn( "read: $f2: $!" ); while ( ) { s/\#.*//; s/\s+/ /g; next if /^Update:/; next if /^Access:/; next if !/[^\s]/; push @form2, $_; } close FORM2; while( scalar(@form1) && scalar(@form2) ) { $f1 = pop @form1; $f2 = pop @form2; return 1 if $f1 ne $f2; } return 1 if ( scalar(@form1) || scalar(@form2) ); return 0; } # Get form name from formfile, formtype. This won't work well # for changelists and jobs with form names of "new", which is # why form-in doesn't just pass it to us in the first place. sub getname { my ( $formfile, $formtype ) = @_; open FORM, $formfile or &warn( "read: $formfile: $!" ); while (
) { return $1 if /^$formtype:\s+([^\s]+)/i; } return ""; } # Propagate timestamps from one form to another. # This is so we can try to keep the timestamps updated during "commit" # rather than overwriting them with the ones we got during "out". # Doesn't work yet for new specs, which don't have these fields at all # during "out"; could modify this subroutine to know where to insert them. # Not a concern for specs that don't have Update/Access fields. sub refresh { my ( $f1, $f2 ) = @_; my @form1 = (); my @form2 = (); open FORM1, $f1 or &warn( "read: $f1: $!" ); while ( ) { $update = $_ if /^Update:/; $access = $_ if /^Access:/; } close FORM1; open FORM2, $f2 or &warn( "read: $f2: $!" ); while ( ) { $_ = $update if $update && /^Update:/; $_ = $access if $access && /^Access:/; push @form2, $_; } close FORM2; open FORM2, '>', $f2 or &warn( "write: $f2: $!" ); foreach( @form2 ) { print FORM2 $_; } close FORM2; } # Run a command and remember its output in case it needs to be logged. sub run { my ( $cmd ) = @_; $log .= $cmd; $log .= ":\n"; $out = `$cmd 2>&1`; $log .= $out; $log .= "\n"; return $out; } # Blat warning to stdout (so end user can see it) and exit 0. # Write more detailed info to a log file in the root so the admin # can try to debug at his/her leisure. sub warn { my ( $err ) = @_; print $err; chdir $root or exit; open LOG, '>>', "log" or exit; $time = localtime; print LOG "$time:\n"; print LOG "$err\n"; print LOG $log; close LOG; exit; }