#!/usr/bin/perl -w

#
# xrcsdiff - graphical rcsdiff using a graphical diff program
#            such as Martin Blais's xxdiff (the default)
#            which can compare up to 3 revisions,
#            and can be obtained from:
#                http://xxdiff.sourceforge.net/
#            or Rudy Wortel's xdiff
#            (which can compare up to 5 revisions, but is not supported well).
#            xdiff can be obtained from:
#                http://reality.sgi.com/rudy_aw/xdiff/
#
# Usage: xrcsdiff [-v <verbosity>] [-b] [-w] [-i] <rcsdiff options> file
#
# The following options are passed on to xxdiff (which passes them on to diff):
#    -b - ignore trailing blanks
#    -w - ignore whitespace
#    -i - ignore case
#  XXX should pass on [-tabs <number>] too
# All other command-line options are passed on to rcsdiff.
#
# The following programs must reside in a non-. directory in $PATH:
#       xxdiff  (or the value of $XDIFF if set)
#       rlog
#       co
# (non-. since these commands may be executed from within a directory
# other than the current directory).
#
# If invoked as xcvsdiff, then cvs is used instead of rcs.
#
# Author: Don Hatch (hatch@hadron.org)
#
# Revision history:
#     Tue Jun  4 21:55:23 PDT 2002
#         Default is now xxdiff instead of xdiff
#     Mon Mar  5 18:39:28 PST 2001
#         Use env XDIFF if set, otherwise "xdiff"
#     Wed Jul 26 12:35:09 PDT 2000
#         Added cvs support (when invoked as xcvsdiff)
#     Fri Jun 23 01:20:01 PDT 2000
#         Initial version
#
# This software may be used for any purpose
# as long as it is good and not evil.
#

use strict;

sub usageError($)
{
    my ($using_cvs) = @_;
    printf STDERR "Usage: $0 [-v <verbosity>] [-b] [-w] [-i] <%s options> file\n",
                  $using_cvs ? "cvs diff" : "rcsdiff";
    exit 1;
}

main:
{
    #
    # Process command line...
    #
    my $verbose = 0;
    my $using_cvs = 0;
    my $xdiff = exists($ENV{"XDIFF"}) ? $ENV{"XDIFF"} : "xxdiff";
    my @xdiffOptions = ();
    my @rcsdiffOptions = ();
    my $fileName;
    {
        $using_cvs = ($0 =~ m/cvs[^\/]*$/); # if 'cvs' in trailing component
        @xdiffOptions = grep {/^-[bwi]/} @ARGV;
        @ARGV = grep {!/^-[bwi]/} @ARGV;

        if (@ARGV >= 2 && $ARGV[0] eq '-v')
        {
            shift @ARGV;
            $verbose = shift @ARGV; # XXX should make sure it's a number
        }
        if (@ARGV == 1 && $ARGV[0] eq '-v')
        {
            usageError($using_cvs);
        }
        if (@ARGV == 0)
        {
            usageError($using_cvs);
        }
        $fileName = pop @ARGV;
        @rcsdiffOptions = @ARGV;
    }

    my $co = 'co -q';
    my $rlog = 'rlog';
    if ($using_cvs)
    {
        $co = 'cvs -n -q update';
        $rlog = 'cvs log';
    }


    if ($verbose >= 1)
    {
        print "\$xdiff          = $xdiff\n";
        print "\@xdiffOptions   = @xdiffOptions\n";
        print "\@rcsdiffOptions = @rcsdiffOptions\n";
        print "\$fileName       = $fileName\n";
        print "\$using_cvs      = $using_cvs\n";
        print "    \$co         = $co\n";
        print "    \$rlog       = $rlog\n";
    }

    #
    # Extract all r<rev> args; these are the revisions to extract.
    #
    my @revsRequested = grep {/^-r/} @rcsdiffOptions;
    @rcsdiffOptions = grep {!/^-r/} @rcsdiffOptions;
    map {s/-r//} @revsRequested;

    if ($verbose >= 1)
    {
        print "\n";
        print "\@revsRequested = @revsRequested\n";
        print "\@rcsdiffOptions = @rcsdiffOptions\n";
    }

    if (@revsRequested == 0)
    {
        #
        # Zero revs requested-- check out the most recent revision
        # and diff it with the existing file.
        # Use rlog to get the version number to put in the xdiff title bar.
        #
        @revsRequested = grep {s/^head: //} split(/\n/, `$rlog -h $fileName`);
        # fall through to @revsRequested==1 case...
    }
    if (@revsRequested == 1)
    {
        #
        # One rev requested-- check out that revision and diff it
        # with the existing file.
        #
        exec "$co @rcsdiffOptions -r@revsRequested -p $fileName | \
              $xdiff @xdiffOptions -N '$fileName @revsRequested' - $fileName";
    }
    else
    {
        #
        # Two or more revisions requested-- check them all out and diff them
        # (currently xdiff handles up to 5 files).
        # This is the only case for which the temp dir is needed.
        # We need to catch interrupts and error returns of all executed
        # commands so that we can clean up the temp dir.
        #

        my $tailFileName = $fileName;
        $tailFileName =~ s/\/*$//; # remove any traling /'s
        $tailFileName =~ s/.*\///; # remove up to final /

        my $tmpDirName = "/var/tmp/xrcsdiff$$";
        $verbose >= 1 && print "tmpDirName=$tmpDirName\n";

        $SIG{HUP} = $SIG{INT} = $SIG{TERM} = sub {
            print STDERR "Cleaning up $tmpDirName\n";
            system("/bin/rm -rf $tmpDirName");
            exit 1;
        };

        system("/bin/mkdir $tmpDirName") and kill 'HUP', $$;
        for my $rev (@revsRequested)
        {
            system("$co @rcsdiffOptions -r$rev -p $fileName > $tmpDirName/$tailFileName$rev") and kill 'HUP', $$;
        }
        my @tailFileNames = map {"$tailFileName$_"} @revsRequested;
        system("(cd $tmpDirName && $xdiff @xdiffOptions @tailFileNames)") and kill 'HUP', $$;
        exec("/bin/rm -rf $tmpDirName");
    } # end case more than one rev specified
} # main
