Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:42.2
gource
cvs-exp.pl
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File cvs-exp.pl of Package gource
#!/usr/bin/perl -w # "cvs log" is reasonably good for tracking changes to a single file, but # for an entire project with lots of branches and tags, it provides _no_ # idea of what's happening on the whole. This cvs-exp.pl works on the output # of cvs log and arranges events chronologically, merging entries as needed. # by keeping track of lists of files and revisions, it figures when in the # sequence of events were various tags and branches created. It prints out a # cute-looking tree at the end to summarize activity in the repository. # # cvs-exp.pl: a global-picture chronological "cvs log" postprocessor # author: Sitaram Iyer <ssiyer@cs.rice.edu> 11jan00, one afternoon's work # Copyright (C) 2000 Rice University # available at http://www.cs.rice.edu/~ssiyer/code/cvs-exp/ # # cvs-exp.pl, a wrapper around (and a spoof of) the "cvs log" command, is a # way of chronologically tracking events in a cvs repository, when you're # lost among lots of branches and tags and little idea of what's going on. # # CHANGELOG # # Bugfix contributed by Adam Bregenzer <adam@bregenzer.net> on Oct 24, 2002 # to allow for spaces in filenames. # # david.fleck@ugs.com (July 2004): # added -before, -changeset, -search, -info, -last, and -remote functions. # # Bugfix from Peter Conrad <conrad@tivano.de> on Nov 30, 2004 # to remove a superfluous line with a "," if a file has a long pathname # so two reasons for writing this: # * I managed a CVS repository for a tolerably largeish project recently, # and had lots of trouble keeping up with the overall state of the # project, especially when people kept making mistakes of committing to # the wrong branches etc. This would've proved quite useful then. # * I like writing _long_ cvs commit descriptions, so a significant part of # documentation in my code happens to be scattered among a gazillion # files in a CVS repository, and I wouldn't mind merging them nicely and # chronologically. # status: # sorting and merging text - sortof. # list of files changed - yes # tag support - yes: 'twas hard # vendor tag support - yes # branch support - yes # NOTE: a newly created branch with no commits inside will not show up. # this is because CVS works that way, and nothing can be done. # # NOTE: tagging files in a new branch will really tag the parent. weird CVS. # # NOTE: create branch, it says CREATE BRANCH, then add some files onto the # trunk, and it says CREATE BRANCH again for *those* files. this is # really what happens, since the branch is created for those files # only at that point. so this isn't a bug either. # # BUGS: infinite nesting in e.g.413. # # BUGS: sorting is not polished up yet, so sometimes entries with almost the # same timestamp are repeated - e.g. with Initial revision and Import use strict; my $opt_files = 1; my $opt_log = 1; my $opt_tree = 1; my $cset = 0; my $log = "log"; my $before = 0; my $lastonly = 0; my $searchstring = 0; my $searchdate = 0; my $case = ""; my @dirs; while ($#ARGV >= 0) { for ($ARGV[0]) { /-nofiles/ && do { shift; $opt_files = 0; last; }; /-nolog/ && do { shift; $opt_log = 0; last; }; /-notree/ && do { shift; $opt_tree = 0; last; }; /-c.*/ && do { shift; $cset = shift; last; }; /-s.*/ && do { shift; $searchstring = shift; last; }; /-i.*/ && do { shift; $searchdate = shift; last; }; /-l.*/ && do { shift; $lastonly = 1; last; }; /-h.*/ && do { usage(); exit(); }; /-b.*/ && do { shift; $before = 1; last; }; /-r.*/ && do { shift; $log = "rlog"; last; }; /.*/ && do { push(@dirs, (shift)); last; }; } } if (-t) { open(CVS, "cvs $log ".join(' ',@dirs)." 2> /dev/null |") || die "can't execute cvs: $!\n"; } else { *CVS = *STDIN; } my $dash = "-"x28; my $ddash = "="x77; my $inheader = 1; my %symbtag = (); my %tagnfiles = (); my ($file, $branch, %d, $txt, $rev, $date, %tagnfiles1, @commitTable); # create an array of hashes (@commitTable) containing entries for each commit. fileentry: while (<CVS>) { chomp; if (!$inheader && ($_ eq $dash || $_ eq $ddash)) { my $k = join(' ', map { s/\s*;$//; "$file:$_" } (split(/\s+/,$branch))); %d = ( 'file' => $file, 'frev' => "$file:$rev", 'txt' => $txt, 'rev' => $rev, 'date' => $date, 'branch' => $k, ); push @commitTable, { %d }; } if ($_ eq $dash) { $inheader = 0; $_ = <CVS>; chomp; s/revision\s*//; $rev = $_; $_ = <CVS>; chomp; $date = $_; $_ = <CVS>; chomp; $txt = ""; $branch = (/^branches:\s*(.*)$/) ? $1 : do { $txt .= "$_\n"; ""; }; } elsif ($_ eq $ddash) { $inheader = 1; undef $file; next fileentry; } else { if ($inheader) { # $file = $1 if (/^Working file: (.*)$/); $file = $1 if (/^RCS file: (.*)(,v)?$/); if (/^\t([^\s]*)\s*:\s*([^\s]*)\s*$/) { die if (!defined($file)); my ($tag,$ver) = ($1,$2); # if $ver has an even number of dot-separateds and the # second-to-last is zero, then wipe it. this is a branch. my @ver = split(/\./,$ver); $ver = join('.',(@ver[0..$#ver-2],$ver[$#ver])) if ($#ver >= 1 && ($#ver % 2) && $ver[$#ver-1] eq "0"); defined($tagnfiles{$tag}) ? do { $tagnfiles{$tag}++ } : do { $tagnfiles{$tag} = 1 }; my @t = (defined($symbtag{$file}{$ver}) ? @{ $symbtag{$file}{$ver} } : ()); push @t, $tag; $symbtag{$file}{$ver} = [ @t ]; } } else { $txt .= "$_\n"; } } } foreach (keys(%tagnfiles)) { $tagnfiles1{$_} = $tagnfiles{$_}; } # backup @commitTable = sort { cmpval($a) cmp cmpval($b) } @commitTable; # merge consecutive entries with the same text - not all entries, note. my $m = "garbage"; my @mtable = (); foreach (@commitTable) { my %entry = %{$_}; if ($m eq $entry{txt}) { # then skip $mtable[$#mtable]{frev} .= " " . $entry{frev}; $mtable[$#mtable]{file} .= " " . $entry{file}; foreach my $tag (@{ $symbtag{$entry{file}}{$entry{rev}} }) { $tagnfiles{$tag}--; unshift (@{$mtable[$#mtable]{tags}},$tag) if ($tagnfiles{$tag} <= 0); } } else { $m = $entry{txt}; $entry{tags} = (); foreach my $tag (@{ $symbtag{$entry{file}}{$entry{rev}} }) { $tagnfiles{$tag}--; unshift (@{$entry{tags}},$tag) if ($tagnfiles{$tag} <= 0); } push @mtable, { %entry }; } } # Having slurped in all the info. from the 'cvs log' command, now figure out # how to output it. my %child = (); my $iter = 0; # return revision numbers of *all* files as they were prior to commit ($cset). if ($before) { unless ($cset) { print "A -changeset value must be provided for this option.\n"; } my (%entry, %latest) = (); xprint("State of repository before change ",sprintf("%06u:\n", $cset)); for (my $i = 0; $i<$cset; $i++) { %entry = %{$mtable[$i]}; foreach (split(/ /,$entry{frev})) { my ($file, $rev) = split(/:/); $latest{$file} =$rev; } } %entry = %{$mtable[$cset]}; my $b = brname($entry{frev}); ($b ne "") && xprint("BRANCH [$b]\n"); xprint("$entry{txt}\n"); xprint("($entry{date})\n"); foreach (sort keys %latest) { xprint($_,":",$latest{$_},"\n"); } foreach (@{$entry{tags}}) { xprint("*** CREATE TAG [$_]" . nfiles($_)); push @{$child{$b}}, "t $_"; } foreach (split(/\s+/,brname($entry{branch}))) { xprint("*** CREATE BRANCH [$_]" . nfiles($_)); push @{$child{$b}}, "b $_"; } xprint("" . ("="x78) . "\n"); exit(0); } # return data only on a single specific change, then exit. # re-arrange the output format to make life easy for cvs-undo.pl if ($cset) { xprint(sprintf("\n%06u:\n", $cset)); my %entry = %{$mtable[$cset]}; my $b = brname($entry{frev}); ($b ne "") && xprint("BRANCH [$b]\n"); xprint("$entry{txt}\n"); xprint("($entry{date})\n"); xprint(join("\n",split(/ /,$entry{frev})) . "\n"); foreach (@{$entry{tags}}) { xprint("*** CREATE TAG [$_]" . nfiles($_)); push @{$child{$b}}, "t $_"; } foreach (split(/\s+/,brname($entry{branch}))) { xprint("*** CREATE BRANCH [$_]" . nfiles($_)); push @{$child{$b}}, "b $_"; } xprint("" . ("="x78) . "\n"); exit(0); } # return data on all changes whose date component matches '$searchdate', then # exit. if ($searchdate) { my $nseen = 0; my $laststring = ""; ($lastonly) && ($laststring = "last instance of "); print "Searching for $laststring\'$searchdate\'\n"; for (my $i = 0; $i <= $#mtable; $i++) { my %entry = %{$mtable[$i]}; if ($entry{'date'} =~ /$searchdate/) { $nseen = $i; unless ($lastonly) { xprint(sprintf("\n%06u:", $i)); my $b = brname($entry{frev}); ($b ne "") && xprint("\nBRANCH [$b]\n"); xprint("\n($entry{date})\n"); xprint(wraprint($entry{frev},77," | ",",") . "\n `" . ("-"x40) . "\n"); xprint("\n$entry{txt}\n"); foreach (@{$entry{tags}}) { xprint("*** CREATE TAG [$_]" . nfiles($_)); push @{$child{$b}}, "t $_"; } foreach (split(/\s+/,brname($entry{branch}))) { xprint("*** CREATE BRANCH [$_]" . nfiles($_)); push @{$child{$b}}, "b $_"; } xprint("" . ("="x78) . "\n"); } } } if (($lastonly) && ($nseen)) { my %entry = %{$mtable[$nseen]}; xprint(sprintf("\n%06u:", $nseen)); my $b = brname($entry{frev}); ($b ne "") && xprint("\nBRANCH [$b]\n"); xprint("\n($entry{date})\n"); xprint(wraprint($entry{frev},77," | ",",") . "\n `" . ("-"x40) . "\n"); xprint("\n$entry{txt}\n"); foreach (@{$entry{tags}}) { xprint("*** CREATE TAG [$_]" . nfiles($_)); push @{$child{$b}}, "t $_"; } foreach (split(/\s+/,brname($entry{branch}))) { xprint("*** CREATE BRANCH [$_]" . nfiles($_)); push @{$child{$b}}, "b $_"; } xprint("" . ("="x78) . "\n"); } print "No relevant entries found\n" if not ($nseen); exit(0); } # return data on all changes whose text component matches '$searchstring', then # exit. if ($searchstring) { my $nseen = 0; print "Searching for \'$searchstring\'\n"; foreach (@mtable) { my %entry = %{$_}; if ($entry{'txt'} =~ /$searchstring/) { $nseen++; xprint(sprintf("\n%06u:", $iter)); my $b = brname($entry{frev}); ($b ne "") && xprint("\nBRANCH [$b]\n"); xprint("\n($entry{date})\n"); xprint(wraprint($entry{frev},77," | ",",") . "\n `" . ("-"x40) . "\n"); xprint("\n$entry{txt}\n"); foreach (@{$entry{tags}}) { xprint("*** CREATE TAG [$_]" . nfiles($_)); push @{$child{$b}}, "t $_"; } foreach (split(/\s+/,brname($entry{branch}))) { xprint("*** CREATE BRANCH [$_]" . nfiles($_)); push @{$child{$b}}, "b $_"; } xprint("" . ("="x78) . "\n"); } $iter++; } print "No relevant entries found\n" if not ($nseen); exit(0); } # return all data, with changeset number prepended. foreach (@mtable) { xprint(sprintf("\n%06u:", $iter)); my %entry = %{$_}; my $b = brname($entry{frev}); ($b ne "") && xprint("\nBRANCH [$b]\n"); xprint("\n($entry{date})\n"); ($opt_files) && xprint(wraprint($entry{frev},77," | ",",") . "\n `" . ("-"x40) . "\n"); xprint("\n$entry{txt}\n"); foreach (@{$entry{tags}}) { xprint("*** CREATE TAG [$_]" . nfiles($_)); push @{$child{$b}}, "t $_"; } foreach (split(/\s+/,brname($entry{branch}))) { xprint("*** CREATE BRANCH [$_]" . nfiles($_)); push @{$child{$b}}, "b $_"; } xprint("" . ("="x78) . "\n"); $iter++; } my %seen = (); do { print "HEAD\n"; print_branch("", ()); } if ($opt_tree); ### End main ### sub nfiles { " <$tagnfiles1{$_[0]} file".($tagnfiles1{$_[0]}==1?"":"s").">\n" } sub xprint { print @_ if ($opt_log); } sub brname { my %b = (); my $line = $_[0]; while ($line =~ s/(.+?):([0-9.]+)\s*//) { my $file = $1; my $ver = $2; $ver =~ s/;$//; my @ver = split(/\./,$ver); pop @ver if ($#ver % 2); $ver = join('.',@ver); my $x = $symbtag{$file}{$ver}; $b{@{$x}[0]} = 1 if (defined($x)); } return join(' ',keys(%b)); } # complicated sort/merge: # sort on timestamp, largely. # however, if two entries have the same hours and minutes, then *merge* on # text and sort on timestamp of the first one anyway. # XXX ABOVE NOT YET FULLY IMPLEMENTED. sub cmpval { my %a = %{$_[0]}; my $s = $a{date}; $s =~ s/^([^:]*:[^:]*:[^:]*):.*/$1/; $s.$a{rev}.$a{txt}.$a{frev}; } sub wraprint { my $n = $_[1]; my $pad = $_[2]; my $join = $_[3]; my $len = 0; my $res = ""; my $line = $_[0]; while ($line =~ s/(.+?:[0-9.]+)\s*//) { if ($len + length($1) + 2 < $n) { do { $res .= "$join "; $len += 1+length($join); } if ($res ne ""); } elsif ($len > 0) { $res .= "$join\n"; $len = 0; } do { $res .= $pad; $len += length($pad); } if ($len == 0); $res .= $1; $len += length($1); } return $res; } sub print_branch { $seen{$_[0]} = 1; my $t = $child{$_[0]}; my @t = (defined($t)?@{$t}:()); shift; my @were_last = @_; my $am_last = 0; foreach (@t) { $am_last = 1 if ($_ eq $t[$#t]); /^(.) (.*)/; my $tb = $1; $_ = $2; print(join('',map {($_?" ":"|")." "} @were_last).($am_last?"`":"|")."- ".($tb eq "b"?"[":"")."$_".($tb eq "b"?"]":"").nfiles($_)) if ($tb ne "b" || !defined($seen{$_})); print_branch($_, (@were_last,$am_last)) if ($tb eq "b" && !defined($seen{$_})); } } sub usage { print "Usage:\n", " cvs-exp.pl [opts] [arguments to cvs log] [dir]\n", "or cvs log | cvs-exp.pl [opts] \n", "\n", "cvs-exp.pl creates a chronological view of the commits, tags, and branching\n", "events within a CVS module or set of CVS modules. cvs-exp.pl uses the 'cvs log'\n", "(or 'cvs rlog') command to gather raw materials for formatted output.\n", "\n", "Options:\n", " -nolog don't do the log; only show the tag tree at the end\n", " -notree vice versa - the default is to print both.\n", " -nofiles don't list files in the log\n", "\n", " The previous three options are overridden by the\n", " following options:\n", "\n", " -before return version information for all files as they were\n", " before a given changeset. (Requires -changeset .)\n", " -changeset nn return information on specific checkin nn\n", " -search str search for all commits whose comments match the regular\n", " expression 'str'\n", " -info str search for all commits whose date or author match the\n", " regular expression 'str'\n", " -last return only the most recent commit matching a search\n", " (only meaningful with the '-info' option)\n", " -remote use 'cvs rlog' rather than 'cvs log'\n", " -help when all else fails\n"; }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor