#!/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; $_ = shift @ARGV; chdir $root or &warn( "Can't cd to $root: $!\n" ); 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; `$p4 revert $formname 2>&1`; `$p4 sync $formname 2>&1`; `$p4 edit $formname 2>&1`; $save = ( (-e $formname) && !&diff($formfile,$formname) && compare($formfile,$formname) ); copy( $formfile, $formname ) or &warn( "copy: $!\n" ); if ( $save ) { `$p4 submit -d \"$user updated $formtype $formname.\" $formname 2>&1`; } } sub out { my ( $user, $formfile, $formtype, $formname ) = @_; if ( !$formname ) { $formname = "form"; } # Get saved form, make sure only differences are comments. `$p4 print -q -o form $formtype/$formname 2>&1`; exit unless -e "form"; chmod 0666, "form" or &warn( "chmod form: $!\n" ); &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: $!\n" ); &refresh( $formfile, $formname ); &warn( "Discarding comments." ) if diff( $formfile, $formname ); # Check in saved form by any means necessary. `$p4 add $formname 2>&1`; `$p4 submit -d \"$user updated $formtype $formname.\" $formname 2>&1`; } # 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: $!\n" ); while ( <FORM1> ) { s/\#.*//; s/\s+/ /g; next if /^Update:/; next if /^Access:/; next if !/[^\s]/; push @form1, $_; } close FORM1; open FORM2, $f2 or &warn( "read: $f2: $!\n" ); while ( <FORM2> ) { 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: $!\n" ); while ( <FORM> ) { 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: $!\n" ); while ( <FORM1> ) { $update = $_ if /^Update:/; $access = $_ if /^Access:/; } close FORM1; open FORM2, $f2 or &warn( "read: $f2: $!\n" ); while ( <FORM2> ) { $_ = $update if $update && /^Update:/; $_ = $access if $access && /^Access:/; push @form2, $_; } close FORM2; open FORM2, '>', $f2 or &warn( "write: $f2: $!\n" ); foreach( @form2 ) { print FORM2 $_; } close FORM2; } # Blat warning to stdout (so end user can see it) and exit. # We don't want to stop the operation, just let the user # know that we bungled retrieving/saving the comments. sub warn { my ( $err ) = @_; print $err; exit; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#3 | 7230 | Sam Stafford |
Improved debugging. Commands executed and their output now get written to a log file in the script root if there are any unexpected problems, so along with the terse error message the user sees the admin has more detailed info to try to debug the problem with. |
||
#2 | 7228 | Sam Stafford |
Catch forms that are updated with comments only; now you don't need to make trivial "real" changes to a form to get comment changes to be saved. Also made change descriptions a little nicer and redid how the workspace is managed (no more +w files, now there's a 'p4 edit' during the form-in). |
||
#1 | 7226 | Sam Stafford |
Experimental trigger for preserving comments and whitespace in forms. Seems to work pretty well on clients and protections; haven't tested much on other spec types. |