#!/usr/local/bin/perl my $verbose = 0; my $tz = -8.0; $USAGE = " USAGE: cvs log | cmd ... sorts log by date, one record per line. Options -v verbose -tz-8 set TZ to -8 for PST. Default tz=$tz. -rtag/PAT Prints only matching revisions. EG. cvs log > cvslog.log perl cmd -rtag/linux22 cvslog.log > cvslog.txt NOTES: cvs log times are in GMT. We print Dates in GMT (dates maybe off by one), time is in LMT. Not exact. Dead files are shown as !tag. Works with gnu perl51, other win-perl will hang, Linux Ok. AUTHOR: GPL(C) Mohsin Ahmed, http://www.cs.albany.edu/~mosh "; while( $_ = $ARGV[0], m/^-/ ){ shift; last if /^--$/; $verbose++, next if m/^-v/; ($tz = $1), next if m,^-tz(.+),; ($rev2tag_filter = $1), next if m,^-rtag/(.+),; die $USAGE if m/^-[h?]/; warn "Unknown option '$_', use -h for help.\n", unshift(@ARGV,$_), last; } # rev2tag: is used by gettag to find tag of nearby versions. my( $key, %tagsym, $rev, $cvsfile, $author, $cvsdate, $cvstime, $fulldate, $printit, $head, $dead, $comment, $branches, %author, %cvsdate, %cvstime, %fulldate, %tag, %comment, %head, %branches, %rev2tag ); $key = 1; $separator_rev = '-' x 28; $separator_file = '=' x 77; LINE: while( <> ){ chomp; next if m/^\s*$/; if( m/^RCS file:/i ){ next LINE; } if( m/revision\s+(\S+)/ ){ # start of new revision, clean up. $comment = ''; $rev = $1; next; } if( m/^symbolic\snames:/ ){ undef %tagsym; # zz. many to one map! TAGSYM: while( <> ){ if( m/^\s+(\S+):\s*(\S+)/ ){ $tagsym{$2} ||= $1; # Keep first name! next TAGSYM; }else{ $tagsym{$head}='head'; redo LINE; } # We should ignore everything till we find ^---- as above. } # Never reached because of above redo. } if( m/^branches:\s/ ){ # revision 1.99.2.11 # branches: 1.99.2.11.2; 1.99.2.11.6; while( m/\s(\S+);/g ){ my $branch = $1; # eg. mosh.c.1.99.2.11.2 => 1.99.2.11 $parent{ $cvsfile.$1 } = $rev; } next LINE; } next LINE if m/^branch:/; next LINE if m/^locks:/; next LINE if m/^access list:/; next LINE if m/^keyword substitution:/; next LINE if m/^description:/; next LINE if m/^total revisions:/; if( m/^head:\s*(\S+)$/ ){ $head = $1; next LINE; } if( m/^Working file:\s*(\S+)/ ){ $cvsfile = $1; next LINE; } # All this in same line. if( s/author:\s+(\S+);// ){ $author = $1; } if( s/date:\s+(\S+)\s+(\S+);\s*// ){ # date: 1964/02/10 01:49:36; $cvsdate = $1; $cvstime = $2; $cvsdate =~ s/\D//g; ## function gmt2tz, for integral TZ only: ($cvstime,$cvsdate)=GMT2TZ($cvstime,$cvsdate) my( $hh, $mm, $ss ) = split( ':', $cvstime ); if( $tz ){ $hh += $tz; if( $hh > 24 ){ $hh -= 24; $cvsdate++; } elsif( $hh < 0 ){ $hh += 24; $cvsdate--; } } $cvstime = sprintf("%02d:%02d:%02d", $hh,$mm,$ss ); $fulldate = $cvsdate . $cvstime; $fulldate =~ s/\D//g; # no non-digits. $dead = (m/state:\s+dead/) ? '!' : ''; s/state:.*//; $printit = 1; next; } if( s/^branches:\s*//i ){ $branches = $_; next LINE; } if( ($_ eq $separator_rev ) || ($_ eq $separator_file )){ next LINE unless $printit; $printit = 0; $tagsym{$rev} ||= ''; # just to avoid warnings. my $tag = $dead . $tagsym{$rev}; my $line = sprintf "%-12s %-14s %10s %-8s %12s [%s]\n", $rev, $tag, $cvsdate, $author, $cvsfile, $comment; next LINE if $pat and $line !~ m/$pat/; print $line if $verbose; $key++; $rev{$key} = $rev; $cvsfile{$key} = $cvsfile; $author{$key} = $author; $cvsdate{$key} = $cvsdate; $cvstime{$key} = $cvstime; $fulldate{$key}= $fulldate; $comment{$key} = $comment; $comment=''; $tag{$key} = $tag; $head{$key} = $head; $branches{$key}= $branches; next LINE if $rev2tag_filter && $tag !~ m/$rev2tag_filter/io; $rev2tag{$cvsfile.$rev} = $tag; next LINE; } #---- Anything else is a comment. s/\s\s+/ /; # squueze spaces if( $comment ){ $comment .= "\\n " . $_; }else{ $comment = $_; } next LINE; } foreach $key (sort cmpverdate keys %author){ my $author = $author{$key} ; my $cvsdate= $cvsdate{$key} ; my $cvstime= $cvstime{$key} ; my $comment= $comment{$key} ; my $cvsfile= $cvsfile{$key} ; my $rev = $rev{$key} ; my $tag = $tag{$key} || gettag($cvsfile,$rev); my $line = sprintf "%-12s %-14s %10s_%s %-8s %12s [%s]\n", $rev, $tag, $cvsdate, $cvstime, $author, $cvsfile, $comment; print $line; } sub cmpverdate { ($fulldate{$a} cmp $fulldate{$b}) || cmpver($a,$b); } # compare versions, eg: 1.1.100 with 1.20.1. sub cmpver { my( @a ) = split( /\./, shift ); my( @b ) = split( /\./, shift ); while( @a or @b ){ my $a = shift @a; my $b = shift @b; my $diff = $b - $a; return $diff if $diff; } return 0; } # Given 1.102, and 1.100 is shipit, then returns shipit+2 # todo: review this carefully. sub gettag { my $cvsfile = shift; my $key = shift; # eg. 1.2.3.5 my( @keylist ) = split( /\./, $key ); # eg. (1 2 3 5) my $keylast = pop @keylist; # eg. 5 my( $offset, $offkey, $newtag ); PREVKEY: for($offset=1; $offset<$keylast;$offset++){ #---- Look back. $offkey = join('.',@keylist,$keylast-$offset ); # warn "looking for key=$key, --offkey=$offkey;\n"; if( $rev2tag{$cvsfile.$offkey} ){ $newtag = $rev2tag{$cvsfile.$offkey} . '+' . $offset; return $newtag; } #---- Look forward $offkey = join('.',@keylist,$keylast+$offset ); # warn "looking for key=$key, ++offkey=$offkey;\n"; if( $rev2tag{$cvsfile.$offkey} ){ $newtag = $rev2tag{$cvsfile.$offkey} . '-' . $offset; return $newtag; } } #---- Not found? Try looking 10 more into the future. for($offset=$keylast;$offset<$keylast+10;$offset++){ # Look forward $offkey = join('.',@keylist,$keylast+$offset ); # warn "looking for key=$key, ++offkey=$offkey;\n"; if( $rev2tag{$cvsfile.$offkey} ){ $newtag = $rev2tag{$cvsfile.$offkey} . '-' . $offset; return $newtag; } } # We dont scan beyond immediate parent for a name. # Earlier we saw the parent with name 'pop': # File 44.33 name is pop. # revision 44.33 # branches: 44.33.2; 44.33.6; # Name of file 44.33.2.1 => pop^2.1 my $keysecondlast = pop @keylist; my $parentkey = join('.',@keylist ); if( $rev2tag{$cvsfile.$parentkey} ){ my $newtag = $rev2tag{$cvsfile.$parentkey} .'^'.$keysecondlast.'.'.$keylast; return $newtag; } #---- return nothing. } # # Gives a previous version 1.1.100 => 1.1.99 # sub prevver { # my( @a ) = split( /\./, shift ); # --$a[@a-1]; # return join(".", @a); # }