eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; # THE PRECEEDING STUFF EXECS perl via $PATH # # $Id: //guest/sandy_currier/utils/p4save.pl#1 $ # # # Copyright (c) 2000, Sandy Currier (sandy@releng.com) # Distributed under the GNU GENERAL PUBLIC LICENSE: # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 1, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # # first, see if unix or NT or what... # need a recent version of perl on NT to have win32 module/config stuff BEGIN: { require 5.004; unless ($Platform{'os'}) { unless ($Platform{'os'} = $^O) { require Config; $Platform{'os'} = $Config::Config{'osname'}; $Platform{'os'} = $Config::Config{'osname'}; } } # bottom layer OS specific variables/constants if ($Platform{'os'}=~/Win/i) { ######################### # win32 ######################### require Win32; # need &Win32::IsWinNT() $Platform{'os'} = "win32"; $Platform{'pd'} = '\\'; if (&Win32::IsWinNT()) { $Platform{'nt'} = 1; } else { $Platform{'nt'} = 0; } } elsif ($Platform{'os'}=~/vms/i) { ######################### # vms ######################### die "vms is currently not a supported platform"; } elsif ($Platform{'os'}=~/os2/i) { ######################### # os2 ######################### die "os2 is currently not a supported platform"; } elsif ($Platform{'os'}=~/Mac/i or (defined($MacPerl::Version) and $MacPerl::Version)) { ######################### # mac ######################### $Platform{'pd'} = ':'; # use this in pathname pattern matching (mac) die "macintosh is currently not a supported platform"; } else { ######################### # unix ######################### $Platform{'os'} = "unix"; $Platform{'pd'} = '/'; } } # # Unbuffer STDERR and STDOUT select(STDERR); $| = 1; # Make STDERR be unbuffered. select(STDOUT); $| = 1; # STDOUT too # # set up some globals $ThisCmd = "p4save"; # this command name # # local variables $P4 = "p4"; $verbose = 0; $err = "***"; $metadata = ""; $metaroot = "//depot/metadata"; $client_p = $label_p = $branch_p = $admin_p = 0; # # now parse any args # the usage message (for -h or on error) $help = "$ThisCmd metadate Function: $ThisCmd will save the state of the metadata argument in perforce. $ThisCmd does this by versioning the metadata in $metaroot/[clients,labels,branches]. For clients, the client spec is saved as the filename client.spec, and the have table is saved as client.have. Executing \"p4 -x client.have sync\" will sync a client to this saved state. For labels, the label spec is saved as label.spec, and the have table is saved as label.have. For branch specs, the branchspec is saved as branch.spec. For admin metadata, namely groups and the protection table, this metadata is saved $metaroot/admin/... NOTE: $ThisCmd uses the default changeset and expects it to be empty. Args: metadata The metadata object that needs to be saved. $ThisCmd should be able to determine the type of the metadata without user input. Switches/Options: -h Prints this help message "; # # parse command line { my($i) = 0; while($i <= $#ARGV) { # scan for a help switch if ($ARGV[$i] =~ /^-h/i) { &DieHelp("", $help); } # scan for switches elsif ($ARGV[$i] =~ /^-n/i) { $printonly = 1; $i++; } elsif ($ARGV[$i] =~ /^-admin/i) { $admin_p = 1; $i++; } # scan for variable definitions (-variable value) elsif ($ARGV[$i] =~ /^-\w+/ and defined($ARGV[$i+1]) and $ARGV[$i+1] !~ /^-[^-]/) { # NOTE: nt has a difficult time with '=' on a command line... # process any variable value switches my($var) = $ARGV[$i]; $var =~ s/^-//; my($value) = $ARGV[$i+1]; if (defined $$var) { $$var = $value; } else { &DieHelp("Unknown parameter '$var'\n", $help); } $i=$i+2; } # catch unsupported switches elsif ($ARGV[$i] =~ /^-/) { &DieHelp("Unsupported switch \"$ARGV[$i]\"\n", $help); } # snarf first arg elsif ($param == 0) { $metadata = $ARGV[$i]; $i++; $param++; } else { &DieHelp("Unsupported argument \"$ARGV[$i]\"\n", $help); } } } # # see if there is an &DieHelp("Must specify a piece of metadata to save") unless ($metadata or $admin_p); # # get the clients, labels, and branches @RawClients = &ExecuteP4Cmd("$P4 clients", $verbose); if ($?) { die "Could not execute '$P4 clients'"; } @RawLabels = &ExecuteP4Cmd("$P4 labels", $verbose); if ($?) { die "Could not execute '$P4 labels'"; } @RawBranches = &ExecuteP4Cmd("$P4 branches", $verbose); if ($?) { die "Could not execute '$P4 branches'"; } # prune them foreach (@RawClients) { /^Client (.+) [0-9]{4}\/[0-9]{2}\/[0-9]{2} /; push @Clients, $1; } foreach (@RawLabels) { /^Label (.+) [0-9]{4}\/[0-9]{2}\/[0-9]{2} /; push @Labels, $1; } foreach (@RawBranches) { /^Branch (.+) [0-9]{4}\/[0-9]{2}\/[0-9]{2} /; push @Branches, $1; } # verify that $metadata matches one of them $safe_metadata = quotemeta($metadata); if (grep(/^$safe_metadata$/, @Clients)) { $client_p = 1; } if (grep(/^$safe_metadata$/, @Labels)) { $label_p = 1; } if (grep(/^$safe_metadata$/, @Branches)) { $branch_p = 1; } # test if ($client_p + $label_p + $branch_p > 1) { &DieHelp("Error: the supplied metadata ($metadata) is not unique."); } # # decide which way to go... if ($admin_p) { &SaveAdmin(); } elsif ($client_p) { &SaveClient($metadata); } elsif ($label_p) { &SaveLabel($metadata); } elsif ($branch_p) { &SaveBranch($metadata); } # # the end exit(0); # # subroutines # # # either exit or submit sub ExitOrSubmit { my($arrayref) = @_; my($hit_p) = 0; # note: opened returns files in depot syntax my(@output) = &ExecuteP4Cmd("$P4 -s opened", $verbose); chomp(@output); my(@files) = grep(/^info:.* default change /, @output); foreach my $file (@files) { $file =~ s|^info: ||; # remove info: token $file =~ s|\#.*$||; # remove revision stuff my($safefile) = quotemeta($file); $hit_p++ if (grep(/^$safefile$/, @{$arrayref})); # only delete our files } if ($hit_p) { system("$P4 submit"); } else { print STDOUT "Nothing to submit - exiting...\n"; exit 0; } } # save out the admin state # assumes that one is superuser sub SaveAdmin { my(@spec, @depotfilelist); # first, get the groups my(@groups) = &ExecuteP4Cmd("$P4 groups", $verbose); if ($?) { die "Could not execute '$P4 groups'"; } chomp(@groups); # now save out each group foreach my $group (@groups) { @spec = &ExecuteP4Cmd("$P4 group -o $group", $verbose); if ($?) { die "Could not execute '$P4 group -o $group'"; } # either add or edit and stuff the file &AddorEditAndStuffFile("$metaroot/admin/$group.spec", \@spec, \@depotfilelist); } # now save out the protection table @spec = &ExecuteP4Cmd("$P4 -s protect -o", $verbose); if (grep(/^error:/, @spec) or $?) { die "Could not execute '$P4 -s protect -o"; } foreach (@spec) { s|^info: ||; # remove info token s|^exit.*$||; # remove exit line s|^\s+super .*$||; # remove super lines for security... } &AddorEditAndStuffFile("$metaroot/admin/protect.table", \@spec, \@depotfilelist); # exit or submit &ExitOrSubmit(\@depotfilelist); } # save out client metadata sub SaveClient { my($client) = @_; my(@depotfilelist); # get the client spec my(@clientspec) = &ExecuteP4Cmd("$P4 client -o $client", $verbose); if ($?) { die "Could not execute '$P4 client -o $client'"; } # get the haves my(@haves) = &ExecuteP4Cmd("$P4 files //...\@$client", $verbose); if ($?) { die "Could not execute '$P4 files //...\@$client'"; } # either add or edit and stuff the file &AddorEditAndStuffFile("$metaroot/clients/$client.spec", \@clientspec, \@depotfilelist); &AddorEditAndStuffFile("$metaroot/clients/$client.have", \@haves, \@depotfilelist); # now submit it (let the form popup) &ExitOrSubmit(\@depotfilelist); } # save out label metadata sub SaveLabel { my($client) = @_; my(@depotfilelist); # get the client spec my(@clientspec) = &ExecuteP4Cmd("$P4 label -o $client", $verbose); if ($?) { die "Could not execute '$P4 label -o $client'"; } # get the haves my(@haves) = &ExecuteP4Cmd("$P4 files //...\@$client", $verbose); if ($?) { die "Could not execute '$P4 files //...\@$client'"; } # either add or edit and stuff the file &AddorEditAndStuffFile("$metaroot/labels/$client.spec", \@clientspec, \@depotfilelist); &AddorEditAndStuffFile("$metaroot/labels/$client.have", \@haves, \@depotfilelist); # now submit it (let the form popup) &ExitOrSubmit(\@depotfilelist); } # save out branch metadata sub SaveBranch { my($client) = @_; my(@depotfilelist); # get the client spec my(@clientspec) = &ExecuteP4Cmd("$P4 branch -o $client", $verbose); if ($?) { die "Could not execute '$P4 branch -o $client'"; } # either add or edit and stuff the file &AddorEditAndStuffFile("$metaroot/branches/$client.spec", \@clientspec, \@depotfilelist); # now submit it (let the form popup) &ExitOrSubmit(\@depotfilelist); } # will push both filelist arrays with the correct file syntax as a side effect... sub AddorEditAndStuffFile { my($filename, $arrayref, $depotfilelistref) = @_; my(@output, $safe_filename, $mapping, $add_p); push @$depotfilelistref, $filename; $safe_filename = quotemeta($filename); # see if the client can map the file - exit if cannot @output = &ExecuteP4Cmd("$P4 -s where \"$filename\"", $verbose); if (grep(/^error:/, @output) or $?) { die "This client cannot map the file '$filename':\n@output"; } else { # else - save the mapping $mapping = $output[0]; } # see if the file exists in the depot @output = &ExecuteP4Cmd("$P4 -s files \"$filename\"", $verbose); if ($?) { die "Could not execute '$P4 -s files \"$filename\"'"; } # file does not exist yet if (grep(/^error: .* - no such file\(s\).$/, @output)) { $add_p++; @output = &ExecuteP4Cmd("$P4 -s add -t text \"$filename\"", $verbose); if ($?) { die "Could not execute '$P4 -s add -t text \"$filename\"'"; } if (grep(/^error:/, @output)) { die "'$P4 -s add -t text \"$filename\"' returned an error"; } } # file already exists else { # first, sync to head @output = &ExecuteP4Cmd("$P4 -s sync \"$filename\"", $verbose); if ((!grep(/^error:.* - file\(s\) up-to-date/, @output) and grep(/^error:/, @output)) or $?) { die "Could not sync file '$filename':\n@output"; } @output = &ExecuteP4Cmd("$P4 -s edit \"$filename\"", $verbose); if ($?) { die "Could not execute '$P4 -s edit \"$filename\"'"; } if (grep(/^error:/, @output)) { die "'$P4 -s edit \"$filename\"' returned an error"; } } # now stuff it - use the mapping { my($realfilename, @tmp, $dir); if ($Platform{'os'} eq "win32") { @tmp = split(/ ([a-zA-Z]:\\)/, $mapping); if ($#tmp == 2) { # the expected number $mapping =~ / ([a-zA-Z]:\\)/; $realfilename = "$1$tmp[2]"; } } else { @tmp = split(/ \//, $mapping); if ($#tmp == 3) { # the expected number $realfilename = "/$tmp[3]"; } } unless ($realfilename) { die "Could not determine the workspace mapping to $filename"; } # yech - must create all the parent dirs if they do not exist... $dir = &DirName(&dos2unix($realfilename)); &CreateMissingDirs($dir); if (!open(OUT, ">$realfilename")) { die "Could not open '$realfilename' for writing"; } foreach (@{$arrayref}) { print OUT $_; } close(OUT); } # now see if it is different enough to not revert unless ($add_p) { my($dir) = &DirName($filename); @output = &ExecuteP4Cmd("$P4 -s diff \"$filename\""); if ($?) { die "Could not execute '$P4 -s diff \"$filename\"'"; } if (grep(/^error:/, @output)) { die "'$P4 -s diff \"$filename\"' returned an error"; } # only look at ^< and ^> lines @output = grep(/(^<)|(^>)/, @output); # now ignore these lines @output = grep(!/^[<>] Access:/, @output); # Access lines in client type specs @output = grep(!/^[<>] $dir\//, @output); # the filename itself in have type files # now if output is not empty, file is different if ($#output == -1) { # revert the file @output = &ExecuteP4Cmd("$P4 -s revert \"$filename\"", $verbose); if (grep(/^error:/, @output) or $?) { die "Could not revert file $filename\n@output"; } } } } # print a help message and die sub DieHelp { my($str, $help) = @_; print STDERR "$err $str\nUsage: $help"; exit(1); } # # Note: this will actually execute any command... # returns the action of the revision of the specified file#revision sub ExecuteP4Cmd { my($script, $verbose, $print_output, $no_error_check, $stream_p) = @_; my(@output); if ($stream_p) { print $stream_p "$vb\n$vb running: $script\n$vb\n" if ($verbose); } else { print STDOUT "$vb\n$vb running: $script\n$vb\n" if ($verbose); } if (!$Platform{'nt'} and $Platform{'os'} eq "win32") { @output = `$script` unless ($printonly); } else { @output = `$script 2>&1` unless ($printonly); } if ($stream_p) { print $stream_p "@output" if ($print_output); } else { print STDOUT "@output" if ($print_output); } if (!$no_error_check and $?) { # now what - just keep going print STDERR "$err $ThisCmd - something happened with '$script'\n$?"; } return(@output); } sub dos2unix { my($filename) = @_; $filename =~ s|\\|/|g; return($filename); } sub DirName { my($string) = @_; # unclear as to what to return when parent dir is null... # maybe best thing to do is return null string... yes, this is best return("") if ($string !~ /\//); $string =~ s|(.*)/[^/]*$|$1|; return("$string"); } sub CreateMissingDirs { my($dir, $relative_pname, $notrecord_p, $dirmode) = @_; my($newdir, $tmp); # if $dirmode is not set, set it $dirmode = 0775 unless ($dirmode); # if dir is a directory, just return return(0) if (-d $dir); # just in case, remove double // and trailing / $dir =~ s|//+|/|g; $dir =~ s|/$||; # remove \./ $dir =~ s|/\./|/|g; $newdir = &DirName($dir); # may need more recursion (the parent directory must be created) if (-l $newdir) { # if the parent is a link to a dir, and the dir does not exist, # error my($srclink) = readlink($newdir); if (! -e $srclink) { die "Error: CreateMissingDirs - parent dir ($newdir) is a dangling link to ($srclink)"; } if (! -d $srclink) { die "Error: CreateMissingDirs - parent dir ($newdir) is a link ($srclink) that does not point to a directory"; return(1); } } elsif (! -d $newdir) { # if it is not a directory $tmp = &CreateMissingDirs($newdir, $relative_pname, $notrecord_p, $dirmode); return(1) if ($tmp); # if a parent didn't get created, just unravel the recursion } # at this point, all non-existing parent dirs have been created # However, if the path has ".."'s in it, then the current directory # might actually now exist. Just return quietly if it does... return(0) if (-d $dir); # at this point, the parent does not exist! # first, print something $tmp = $dir; if ($relative_pname) { $tmp =~ s|^$relative_pname/||; } # create the dir $tmp = mkdir($dir, $dirmode); if (!$tmp) { die "Error: CreateMissingDirs - Unable to mkdir '$dir' (mode=$dirmode)\n$!"; return(1); } return(0); }