#! /usr/bin/env perl # cvs blame inspired by Bonsai # Author: Bernd Gehrmann <bernd@physik.hu-berlin.de> =head1 NAME cvsblame - Shows a blame-annotated representation of a CVS controlled file in Konqueror. =head1 SYNOPSIS cvsblame <filename> =head1 DESCRIPTION cvsblame opens Konqueror to display the output of cvs annotate of a cvs controlled file. When the mouse is on a revision number shown in the second column, a popup with the respective log message appears. In the popup, a proper mailto: link to the author of a revision can be created as follows: In your home directory, make a file .cvsblame. In that file, enter for each repository you are working with a line like accounts :pserver:gehrmab@cvs.kde.org:/home/kde /home/bernd/.kdeaccounts where the accounts file contains a simple list of cvs usernames in the first column and the respective mail address in the second. =head1 BUGS =over 4 =item Does not really work for filenames which are not in the current directory =item Is a hack. Really. =back =head1 AUTHOR Bernd Gehrmann <bernd@physik.hu-berlin.de> =cut $file = $ARGV[0]; $outputfile = `tde-config --path tmp` || './#'; # if we put the file in the cwd, then we keep a '#' at the beggining to help CVS ignore it ($outputfile) = split(/:/,$outputfile); chomp $outputfile; $outputfile .= "cvsblame.$$.html"; $configfile = $ENV{HOME}. "/.cvsblame"; $rootfile = "`pwd`/CVS/Root"; $cvsroot = `cat "$rootfile"`; chop $cvsroot; # # Look for a username -> mail address mapping # if (open(CONFIG, $configfile)) { while (<CONFIG>) { if (/accounts\s*([^\s]*)\s+([^\s]*)/) { if ($1 eq $cvsroot) { $accountfile = $2; } } } close CONFIG; } if ($accountfile) { open(ACCOUNTS, $accountfile) || die "Account file not found: $accountfile"; while (<ACCOUNTS>) { if (/([^\s]*)\s+([^\s].*[^\s])\s+([^\s]+)/) { $mail{$1} = "$2 <$3>"; } elsif (/([^\s]*)\s+([^\s]*)/) { $mail{$1} = $2; } } } # # The real work, first the html header # open(OUTPUT, ">$outputfile"); print OUTPUT <<EOF; <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Blame annotation for $file</title> </head> <script type="text/javascript"> function hidelog(event) { var target = event.relatedTarget; while (target && target.id != "popup") target = target.offsetParent; if (target) return; target = event.target; while (target.id != "popup") target = target.offsetParent; target.style.visibility = "hidden"; } function init() { document.getElementById("popup").addEventListener("mouseout", hidelog, false); } function poplog(target, rev) { var popup = document.getElementById("popup"); var x = 120, y = 0; while (target && target != document) { x += target.offsetLeft; y += target.offsetTop; target = target.offsetParent; } popup.style.pixelLeft = x; popup.style.pixelTop = y; popup.innerHTML = rev; popup.style.visibility = "visible"; return true; } EOF # # Translate information from cvs log in javascript stuff # $revision = ""; open (LOG, "cvs log \"$file\"|"); while (<LOG>) { chop; $line = $_; if ($line =~ /^revision (.*)/) { $revision = $1; $revstr = "log$revision"; $revstr =~ s/\./_/g; } elsif ($line =~ /date: ([^;]*);\s*author: ([^;]*);.*/) { $date = $1; $author = $2; if ($mail{$author}) { $author = "<a href=\\\"mailto:$mail{$author}\\\">$author</a>"; } $content = "<b>$revision</b> $author <b>$date<b>"; } elsif ($line eq "----------------------------" && !($revision eq "")) { print OUTPUT "var $revstr = \"$content\";\n"; } elsif ($line eq "=============================================================================" && !($revision eq "")) { print OUTPUT "var $revstr = \"$content\";\n"; } else { $line =~ s/\"/\\\"/g; $line =~ s/&/&/g; $line =~ s/</</g; $line =~ s/>/>/g; $content = "$content<br>$line"; } } close LOG; print OUTPUT <<EOF; </script> <style type="text/css"> body { background: #eeeee0; } #popup { position: absolute; background: #ffcc99; border: 1px solid #80664D; padding: 2px; } a:link { text-decoration: none; } a:hover { text-decoration: underline; } </style> <body text="#000000" onload="init()"> <h1>$file</h1> <table border=0 cellpadding=0 cellspacing=0 width="100%"> EOF # # Information from cvs annotate # $color = 1; $lineno = 1; $oldrevision = ""; $oldlineno = ""; $oldrevstr = ""; open (ANNOTATE, "cvs annotate \"$file\" 2>/dev/null|"); while (<ANNOTATE>) { chop; $line = $_; $revision = substr $line, 0, 13; $revision =~ s/\s//g; $author = substr $line, 14, 9; $author =~ s/\s//g; $date = substr $line, 23, 9; $content = substr $line, 35; $content =~ s/\&/&/g; $content =~ s/\</</g; $content =~ s/\>/>/g; $revstr = "log$revision"; $revstr =~ s/\./_/g; if ($revision eq $oldrevision) { if ($lineno == $oldlineno+20) { $linkstr = "<span onmouseover=\"return poplog(this, $revstr)\"'>"; $linkendstr = "</span>"; $revauthor = "$author $revision"; $oldlineno = $lineno; } else { $linkstr = ""; $linkendstr = ""; $revauthor = ""; } } else { $color = ($color == 0)? 1 : 0; $linkstr = "<span onmouseover=\"return poplog(this, $revstr)\"'>"; $linkendstr = "</span>"; $revauthor = "$author $revision"; $oldlineno = $lineno; $oldrevision = $revision; } print OUTPUT "<tr><td nowrap style=\"background: ", ($color==1)? "#EEEEE0" : "#FFFFCC", "\"><pre>"; print OUTPUT sprintf '%-5i%s%-14s%s%s', $lineno, $linkstr, $revauthor,$linkendstr, $content; print OUTPUT "</pre></td></tr>\n"; $lineno++; } close ANNOTATE; # # Finally, the html footer # print OUTPUT <<EOF; </table> <div id="popup" style="visibility: hidden"></div> </body> </html> EOF close OUTPUT; system("kfmclient openProfile webbrowsing $outputfile"); exit 0;