#!/usr/bin/perl -w # -*- perl -*- use P4CGI ; use strict ; # ################################################################# # CONFIGURATION INFORMATION # All config info should be in P4CGI.pm # ################################################################# # # P4 change browser # View list of changes for selected part of depot # ################################################################# ##### # This is (or was) the most complicated and insane script in P4DB. # The reason for this is that features where added and added and added until # the script became impossible to maintain and no more features could # be added. Despite this I still wanted MORE FEATURES and finally I # decided to start all over again and see if it is possible to add the # new features if I rewrote it all. And maybe, just maybe, I will # manage to make it maintainable this time. We will see...... # Jan 7 - 2000/Fredric #### ####### # Arguments: # # FSPC # File specification. Should start with //. Can be more than one file spec # in which case they are concatenated (no space, but start each file spec # with //). # The file specification should not contain any label or revision numbers. # # LABEL # Label specification. Label for which changes should be listed. # There can be only one label. # # NOTE! FSPC or LABEL must be specified. # # STATUS # Status of changes. Allowed values are "submitted" and "pending". # If not supplied "submitted" is assumed. # # EXLABEL (optional) # Specify a label to exclude. Same format as LABEL. The changes made for # this label are excluded from list. # # MAXCH (optional) # Max changes to display. To avoid very large html pages this argument # limits the number of changes displayed at one page. # If not specified there is a default value used (from config file) # # FIRSTCH (optional) # When MAXCH is specified this specifies change to start at (for second # and subsequent pages) # # CHOFFSET (optional) # Number of changes already displayed. # # SEARCHDESC (optional) # View only changes with pattern in description # # SEARCH_INVERT (optional) # If specified, invert pattern search # # USER (optional) # View only changes for users specified in comma-separated list # # GROUP (optional) # View only changes for users in comma-separated list of groups # # CLIENT (optional) # View only changes for clients specified in comma-separated list # # SHOWREFERENCED (optional) # If present and set to "Y" will try to display description for changes # referred to in change description # ###### my $MAGIC_RED=":::RED:::~~~" ; ### ### ### Get command arguments ### ### ### # # Get file spec argument # my $filespec = P4CGI::cgi()->param("FSPC") ; my $FSPC_WasSpecified = $filespec ; $filespec = "//..." unless defined $filespec ; $filespec =~ s/\s*\+\s*\/\//\/\//g ; # replace <space>+<space>// with // # where <space> is 0 or more whitespace charcaters my @FSPC = map { if($_) { "//".$_ ; } else { () ; } ; } split("//", $filespec ) if defined $filespec ; # # Get label argument # my $LABEL = P4CGI::cgi()->param("LABEL") ; if(defined $LABEL and $LABEL eq "-") { $LABEL = undef ; } ; # # Check that FSPC or LABEL is specified # unless(defined $LABEL or defined $FSPC_WasSpecified) { &P4CGI::bail("File spec OR label must be specified") ; } # # Get label to exclude # my $EXLABEL = &P4CGI::cgi()->param("EXLABEL") ; if(defined $EXLABEL and $EXLABEL eq "-") { $EXLABEL = undef ; } ; # # Get status # my $STATUS = &P4CGI::cgi()->param("STATUS") ; unless(defined $STATUS) { $STATUS = "submitted" ; } ; # # Get max changes to display # my $MAXCH = P4CGI::cgi()->param("MAXCH") ; $MAXCH = &P4CGI::MAX_CHANGES() unless(defined $MAXCH) ; # # Get first change No. to display and offset from start # my $FIRSTCH ; my $CHOFFSET=0 ; if(defined $MAXCH) { $FIRSTCH = P4CGI::cgi()->param("FIRSTCH") ; $CHOFFSET = P4CGI::cgi()->param("CHOFFSETDISP") ; } my $SHOWREFERENCED = P4CGI::cgi()->param("SHOWREFERENCED") ; $SHOWREFERENCED = undef if defined $SHOWREFERENCED and $SHOWREFERENCED ne "Y" ; # # Get search data, user and client parameters # my $SEARCHDESC = &P4CGI::cgi()->param("SEARCHDESC") ; $SEARCHDESC=undef if defined $SEARCHDESC and $SEARCHDESC eq "" ; my $SEARCH_INVERT = &P4CGI::cgi()->param("SEARCH_INVERT") ; my $USER = &P4CGI::cgi()->param("USER") ; { my @tmp = &P4CGI::cgi()->param("USERS") ; if(@tmp) { if(defined $USER) { $USER .= "," . join(',',@tmp) ; } else { $USER = join(',',@tmp) ; } } } $USER=undef if defined $USER and $USER eq "" ; my $GROUP = &P4CGI::cgi()->param("GROUP") ; { my @tmp = &P4CGI::cgi()->param("GROUPS") ; if(@tmp) { if(defined $GROUP) { $GROUP .= "," . join(',',@tmp) ; } else { $GROUP = join(',',@tmp) ; } } } $GROUP=undef if defined $GROUP and $GROUP eq "" ; my $CLIENT = &P4CGI::cgi()->param("CLIENT") ; $CLIENT=undef if defined $CLIENT and $CLIENT eq "" ; ### ### Sub getChanges ### # This subroutine is used to get a set of changes from depot. The parameter is a hash containing # a set of switches: # -long If set, get changes in "long" format (i.e. full description, # not only the first 27 chars) # -file File spec for changes # -firstch First change to look for (or "offset") # -maxch Max changes to get # -status Status ("submitted" or "pending") # -label Label for file spec # -lastch Reference to scalar to receive last change parsed # -lastrch Reference to scalar to receive last change parsed and returned in result # -resultto A reference to a hash to receive result # -select A reference to a subroutine to call that determine if a change should be included # in list. The subroutine gets parameters: (<change>,<date>,<user>,<client>,<description>) # The <description> parameter is passed as a reference and can be modified # The subroutine should return true if the change should be included. # NOTE! The -select parameter is very important to understand if you plan to # understand more of this code. # # Another important thing to understand is that this subroutine getChanges is frequently called # more than once. # sub getChanges(%) { my %pars = @_ ; my $long ; # defined if -l flag my $filespec = "" ; # file spec my $firstch = &P4CGI::CURRENT_CHANGE_LEVEL() ; # first change to look for my $maxch = 0 ; # max no changes my $status="submitted" ; # status my $label ; # label my $rhash ; # result my $select ; # selection funtion my $lastch ; # Last change parsed my $lastrch ; # Last change parsed and returned my $linkedch ; # linked ch ref my $k ; foreach $k (keys %pars) { $k = lc($k) ; $k eq "-long" and do { $long = $pars{$k} ; next } ; $k eq "-file" and do { $filespec = $pars{$k} ; next } ; $k eq "-firstch" and do { $firstch = $pars{$k} ; next } ; $k eq "-maxch" and do { $maxch = $pars{$k} ; next } ; $k eq "-status" and do { $status = $pars{$k} ; next } ; $k eq "-label" and do { $label = $pars{$k} ; next } ; $k eq "-resultto" and do { $rhash = $pars{$k} ; next } ; $k eq "-select" and do { $select = $pars{$k} ; next } ; $k eq "-lastch" and do { $lastch = $pars{$k} ; next } ; $k eq "-lastrch" and do { $lastrch = $pars{$k} ; next } ; $k eq "-magiclinked" and do { $linkedch = $pars{$k} ; next } ; } ; my $tmpLastch ; $lastch = \$tmpLastch unless defined $lastch ; $lastrch = \$tmpLastch unless defined $lastrch ; if($long) { $long = " -l " ; } else { $long = "" } ; if($label) { $filespec .= "\@$label" ; } else { if ($firstch) { $filespec .= "\@$firstch" ; } ; } ; if($maxch) { $maxch = " -m $maxch " ;} else { $maxch = "" ; } ; if($status eq "pending") { $filespec = "" ; } ; if($filespec =~ /\s/) { $filespec = "\"$filespec\"" ; } ; my $command = "changes $long -s $status $maxch $filespec" ; local *P4 ; my $n = 0 ; &P4CGI::p4call(*P4,$command) ; $$lastrch = 0 ; while(<P4>) { chomp ; if(/Change (\d+) on (\S+) by (\w+)\@(\S+)/) { $n++ ; my ($change,$date,$user,$client) = ($1,$2,$3,$4) ; $$lastch = $change ; my $desc = "" ; if($long) { <P4> ; while(<P4>) { chomp ; last if length($_) == 0 ; s/^\t// ; if(length($desc) > 0) { $desc .="\n" ; } ; $desc .= $_ ; } # &P4CGI::ERRLOG("select: $select") ; if(defined $select) { next unless &$select($change,$date,$user,$client,\$desc) ; } my @ch; $desc = "<pre>" . &P4CGI::magic(&P4CGI::fixSpecChar($desc),\@ch) . "</pre>" ; $desc =~ s/$MAGIC_RED(.*?)$MAGIC_RED/<font color=red>$1<\/font>/gi ; if(defined $linkedch and @ch > 0) { $$linkedch{$change} = \@ch ; } ; } ; $$lastrch = $change ; $$rhash{$change} = [$date,$user,$client,$desc] ; } } close P4 ; return $n ; } ; ### get target ### my %extraUrlOptions ; if(&P4CGI::CHANGES_IN_SEPPARATE_WIN()) { $extraUrlOptions{"-target"}="CHANGES" ; } ### ### ### Fix page title ### ### ### my $title = "Changes for " ; if($FSPC_WasSpecified) { $title .= "<br><tt>" . join("<br>",@FSPC) . "</tt>" ; if(defined $LABEL) { $title .= "<br>and label <tt>$LABEL</tt>" ; } } else { $title .= "label <tt>$LABEL</tt>" ; } ; if(defined $EXLABEL) { $title .= "<br>excluding changes for label <tt>$EXLABEL</tt>" ; } if(defined $CHOFFSET and $CHOFFSET > 0) { $title .= "<br><small>(offset $CHOFFSET from top)</small>" ; } ; if(defined $USER) { $title .= "<br>user: <tt>$USER</tt>" ; } ; if(defined $GROUP) { $title .= "<br>group: <tt>$GROUP</tt>" ; # } ; if(defined $CLIENT) { $title .= "<br>client: <tt>$CLIENT</tt>" ; # } ; if(defined $SEARCHDESC) { my $not="" ; if(defined $SEARCH_INVERT) { $not = " does not" } $title .= "<br>where description$not match: <tt>$SEARCHDESC</tt>" ; } ; if($STATUS eq "pending") { $title .= "<br>(status: pending)" ; } ; ### ### ### Get changes to exclude (if any) ### ### ### local *P4 ; my %excludeChanges ; my $f ; my $lastChangeInLabel = 0 ; if(defined $EXLABEL ) { getChanges(-label=>$EXLABEL, -resultto=> \%excludeChanges) ; my $n = scalar keys(%excludeChanges) ; my @tmp = sort { $b <=> $a } keys %excludeChanges ; $lastChangeInLabel = $tmp[0] ; &P4CGI::ERRLOG("Exclude from label \"$EXLABEL\":$n lastCh:$lastChangeInLabel") ; } ; ### ### ### Start page ### ### ### my @legend ; push @legend, "<b>Change No.</b> -- see details of change", "<b>User</b> -- Information about user" ; unless(defined $SHOWREFERENCED) { push @legend,&P4CGI::ahref(-url => &P4CGI::cgi()->self_url . "&SHOWREFERENCED=Y", "Show description of changes referenced in change description") ; } &P4CGI::SET_HELP_TARGET("changeList") ; print "", &P4CGI::start_page($title,&P4CGI::ul_list(@legend)) ; ### ### ### Get changes ### ### ### my %changes ; my $cchange = "0" ; my $oldestSafeCh = 0 ; # The last change in %changes that is "safe" (after this change changes # may be missing) my $gotAll="No" ; # Set to "Yes" if there are no more changes to display my %magicLinks ; ### ### If "pending" get all pending changes ### if($STATUS eq "pending") { # Pending. All file and label specifications ignored... $title = "Pending changes" ; my $choffstr = "" ; my %chs ; getChanges(-status => "pending", -long => 1, -resultto => \%chs) ; my $ch ; foreach $ch (sort { $b <=> $a } keys %chs) { my ($date,$user,$client,$desc) = @{$chs{$ch}} ; $changes{$ch} = "<dt> ". &P4CGI::ahref("-url" => "changeView.cgi", %extraUrlOptions, "CH=$ch", "Change $ch") . "\n" ; $changes{$ch} .= " $date by ".&P4CGI::ahref("-url" => "userView.cgi", "USER=$user", $user) . "\@" ; $changes{$ch} .= &P4CGI::ahref("-url" => "clientView.cgi", "CLIENT=$client", $client) . "\n<dd>" ; $changes{$ch} .= $desc ; } } ### ### If not "pending" get all changes ### else { my $max = $MAXCH ; # max ## ## Create subroutines for selection ## my @selectFuncs ; # Variable to hold subroutines if(defined $SEARCHDESC) { # Search description $max = (1+$max)*10 ; my $s = "($SEARCHDESC)" ; # $s =~ s/\s*\+\s*/|/g ; $s =~ s/\./\\\./g ; $s =~ s/\*/.\*/g ; $s =~ s/\?/./g ; $s =~ s/\s+/[\\\s+\n]+/g ; my $sq = $s ; $sq =~ s/\n/\\n/g ; &P4CGI::ERRLOG("select: ".$sq) ; if(defined $SEARCH_INVERT) { push @selectFuncs , sub { my ($ch,$date,$user,$client,$desc) = @_ ; return $$desc !~ /$s/gi ; } ; } else { push @selectFuncs , sub { my ($ch,$date,$user,$client,$desc) = @_ ; return $$desc =~ s/$s/$MAGIC_RED$1$MAGIC_RED/gi ; } ; } } ; if(defined $GROUP) { # Group(s) specified my @grps = split(',',$GROUP) ; while(@grps) { my $grp = shift @grps ; &P4CGI::ERRLOG("group: $grp") ; my %data ; &P4CGI::p4readform("group -o $grp",\%data) ; if(exists $data{"Subgroups"}) { push @grps,split("\n",$data{"Subgroups"}) ; } my $u ; foreach $u (split("\n",$data{"Users"})) { if(defined $USER) { $USER .= ",$u"; } else { $USER = "$u" ; } } } } if(defined $USER) { # User(s) specified my %users ; my $usersToCheck = 0 ; foreach (split(',',$USER)) { $users{$_} = 1 ; $usersToCheck++ ; } &P4CGI::ERRLOG("users: ".join(",",(keys %users))) ; push @selectFuncs , sub { my ($ch,$date,$user,$client,$desc) = @_ ; return exists $users{$user} ; } ; my @users ; &P4CGI::p4call(\@users,"users") ; $max *= 3+int(@users/(5*$usersToCheck)) ; } ; if(defined $CLIENT) { # Client specified my %clients ; my $clientsToCheck = 0 ; foreach (split(',',$CLIENT)) { $clients{$_} = 1 ; $clientsToCheck++ ; } &P4CGI::ERRLOG("client: $CLIENT") ; push @selectFuncs , sub { my ($ch,$date,$user,$client,$desc) = @_ ; return exists $clients{$client} ; } ; my @clients ; &P4CGI::p4call(\@clients,"clients") ; $max *= 3+int(@clients/(5*$clientsToCheck)) ; } ; if((keys %excludeChanges) > 0) { # Exclude changes from a list push @selectFuncs , sub { my ($ch,$date,$user,$client,$desc) = @_ ; return ! exists $excludeChanges{$ch} ; } ; } ## ## Create a select subroutine for selection functions defined (if any) ## my $selectFunc ; if(@selectFuncs > 0) { $selectFunc = sub { my @params = @_ ; foreach (@selectFuncs) { return undef unless &$_(@params) ; } return 1 ; } } ## ## Set max changes to return for each search ## $max = 2000 if $max < 2000 ; # There is no point searching less than 2000 at the time. # Absolutely no point. my %chLevel ; # Store how far back we have traced by file spec my %ended ; # Set to true for a file spec where we have hit the end # HINT: We can merge changes from more than one file spec my $noFSPCsNotEnded = 0 ; # Store number of file speces that has not ended (so we know # when there is no point to keep trying) ## ## Initialize variable above ## my $firstch = &P4CGI::CURRENT_CHANGE_LEVEL() ; # First change we are interested in $firstch = $FIRSTCH if defined $FIRSTCH ; # Set if parameter FIRSTCH given my $fspc ; foreach $fspc (@FSPC) { $chLevel{$fspc} = $firstch ; # Set level to current for all filespec's $ended{$fspc} = 0 ; # Not ready with flespec yet... $noFSPCsNotEnded++ ; # Increment filespecs not ready } my %chs ; # result hash while(1) { # Loop until ready ## ## Loop over each file spec ## $oldestSafeCh = 0 ; my $filespec ; foreach $filespec (@FSPC) { # for each filespec..... next if $ended{$filespec} ; # Skip if all changes read for file spec my $lastIncludedCh ; # Store last change included in selection # # Set up parameters for getChanges() # my %params = (-file => $filespec , -resultto => \%chs, -long => 1, -maxch => $max+1, -select => $selectFunc, -lastch => \$chLevel{$filespec}, -lastrch => \$lastIncludedCh, -firstch => $chLevel{$filespec}, -magiclinked => \%magicLinks ) ; if(defined $LABEL) { $params{"-label"} = $LABEL ; } # # Call getChanges # my $gotCh = getChanges(%params) ; # # Evaluate returned data # if($gotCh != ($max+1)) { # Did we get all changes we asked for? If no.... $ended{$filespec} = 1 ; # ... there is no more data for this file spec $noFSPCsNotEnded-- ; &P4CGI::ERRLOG("file spec \"$filespec\" ended") ; } else { if($lastIncludedCh > $oldestSafeCh) { # Update oldes safe ch. $oldestSafeCh = $lastIncludedCh ; } } } # End loop over each filespec if($noFSPCsNotEnded == 0) { # Did we get all changes there are for the filespecs? # No more changes for these filespecs $gotAll = "Yes" ; last ; } # Count number of changes that we can "trust" (for more than # one file spec we reach different number of changes back in time) my $okchs = 0 ; my $c ; foreach $c (keys %chs) { $okchs++ if $c >= $oldestSafeCh ; } &P4CGI::ERRLOG("okchs: $okchs, max: $max") ; last if $okchs >= $MAXCH ; # Did we get enough changes... } ## ## Build data for changes to display ## my $changesDisplayed=0 ; my $ch ; my @sorted = sort { $b <=> $a } keys %chs ; while($ch = shift(@sorted)) { if((exists $changes{$ch}) or (defined $FIRSTCH and ($ch > $FIRSTCH))) { &P4CGI::ERRLOG("skip ch $ch") ; } else { my ($date,$user,$client,$desc) = @{$chs{$ch}} ; $changes{$ch} = "<dt> ". &P4CGI::ahref("-url" => "changeView.cgi", %extraUrlOptions, "CH=$ch", "Change $ch") . "\n" ; $changes{$ch} .= " $date by ".&P4CGI::ahref("-url" => "userView.cgi", "USER=$user", $user) . "\@" ; $changes{$ch} .= &P4CGI::ahref("-url" => "clientView.cgi", "CLIENT=$client", $client) . "\n<dd>" ; $changes{$ch} .= $desc ; $changesDisplayed++ ; if(defined $SHOWREFERENCED and exists $magicLinks{$ch}) { my $refch = "<small><dl compact>" ; my $n = 0 ; my $c ; foreach $c (@{$magicLinks{$ch}}) { my %data ; &P4CGI::p4readform("change -o $c",\%data) ; if(exists $data{"Description"}) { $n++ ; my $d = &P4CGI::fixSpecChar($data{"Description"}) ; $d =~ s/\n/<br>\n/g ; $c = &P4CGI::ahref("-url" => "changeView.cgi", %extraUrlOptions, "CH=$c", "Change $c") ; if(exists $data{"User"}) { $c .= " by " . &P4CGI::ahref("-url" => "userView.cgi", "USER=$data{User}", $data{"User"}) ; } $refch .= "<dt>$c:\n<dd><tt>$d</tt>" ; } } if($n > 0) { $changes{$ch} .= "$refch</dl></small><hr width=50% align=left>" ; } } } if($changesDisplayed == $MAXCH) { if(@sorted > 0) { $gotAll = "No" ; } last ; } } } ; ### ### ### Start print ### ### ### print "<dl>\n" ; my $debug_size = scalar keys %changes ; &P4CGI::ERRLOG("$debug_size changes to display") ; my $ch ; my $maxch = $MAXCH ; my $lastch = 1 ; #my $skipped = 0 ; foreach $ch (sort { $b <=> $a } keys %changes) { last if ($maxch == 0) ; if($ch < $oldestSafeCh) { # Can not happend??? &P4CGI::ERRLOG("ch:$ch oldestSafeCh:$oldestSafeCh") ; # DEBUG $maxch = 0 ; last ; } ; $maxch-- ; $lastch=$ch-1 ; $CHOFFSET++ ; if(defined $EXLABEL and $ch < $lastChangeInLabel) { print "</dl><table width=90% align=center cols=3 bgcolor=", &P4CGI::HDRFTR_BGCOLOR(), "><tr><th width=4 align=center><hr>Last change in Label $EXLABEL is $lastChangeInLabel<hr>", "</th></tr></table><dl>\n" ; $lastChangeInLabel = 0 ; } print $changes{$ch} ; } print "</dl>\n" ; &P4CGI::ERRLOG("gotAll:$gotAll maxch:$maxch") ; if(($maxch == 0) and ($gotAll ne "Yes")) { my @params = ("STATUS=$STATUS", "FIRSTCH=$lastch", "CHOFFSETDISP=$CHOFFSET", "MAXCH=$MAXCH") ; if(defined $EXLABEL) { push @params,"EXLABEL=$EXLABEL" ; } ; if(defined $LABEL) { push @params,"LABEL=$LABEL" ; } ; if(defined $USER) { push @params,"USER=$USER" ; } ; if(defined $GROUP) { push @params,"GROUP=$GROUP" ; } ; if(defined $SEARCHDESC) { push @params,"SEARCHDESC=$SEARCHDESC" ; } ; if(defined $SEARCH_INVERT) { push @params,"SEARCH_INVERT=1" ; } ; if(defined $SHOWREFERENCED) { push @params,"SHOWREFERENCED=$SHOWREFERENCED" ; } ; if($FSPC_WasSpecified) { push @params,"FSPC=".P4CGI::cgi()->param("FSPC") ; } print "", &P4CGI::ahref("-url","changeList.cgi", @params, "<b>More....</b>") ; } print "",&P4CGI::end_page(); # # That's all folks #