Subversion Repositories DevTools

Rev

Rev 7300 | Blame | Compare with Previous | Last modification | View Log | RSS feed

########################################################################
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
#
# Module name   : jats_vcsdiff.pl
# Module type   : Makefile system
# Compiler(s)   : Perl
# Environment(s): jats
#
# Description   : Create two views and DIFF them
#                 The views can be any type understood by JATS
#                 Can:
#                   - Compare with MD5SUms
#                   - Invoke BeyondCompare
#
#......................................................................#

require 5.008_002;
use strict;
use warnings;

use Pod::Usage;
use Getopt::Long;

use JatsError;
use JatsSystem;
use JatsRmApi;
use DBI;
use FileUtils;
use Cwd;

my $VERSION = "1.0.0";                      # Update this

#
#   Options
#
my $opt_debug   = $ENV{'GBE_DEBUG'};        # Allow global debug
my $opt_verbose = $ENV{'GBE_VERBOSE'};      # Allow global verbose
my $opt_help = 0;
my $opt_new_label;
my $opt_old_label;
my $opt_md5check;
my $opt_mode;
my $opt_diff;
my $opt_rtag_id;
my $opt_package_name;
my @opt_options;
my $opt_patch;

#
#   Globals - Provided by the JATS environment
#
my $USER            = $ENV{'USER'};
my $UNIX            = $ENV{'GBE_UNIX'};
my $TMP             = $UNIX ? "/tmp" : $ENV{'TMP'};
my $MACHINENAME     = $ENV{'GBE_HOSTNAME'};

#
#   Globals
#
my $Name            = 'BeyondCompare';
my $DiffProg;
my @DiffArgs;
my $DiffWait;
my @view_tags;
my @view_commands;
my @cleanFiles;

my $RM_DB;                      # Database interface
my $package_name;               # Selected package name.
my $package_ver;                # Selected package version.
my $package_vcs;                # Selected package VCS tag.
my $package_vcs_base;           # Selected package base path.
my $package_release_branch;     # Selected package release branch or nul if released on trunk.


#-------------------------------------------------------------------------------
# Function        : Mainline Entry Point
#
# Description     :
#
# Inputs          :
#

#
#   Parse the user options
#
my $result = GetOptions (
                'help|h:+'          => \$opt_help,              # Help Level
                'manual:3'          => \$opt_help,              # Help Level
                "verbose:+"         => \$opt_verbose,           # Verbosity
                "debug:+"           => \$opt_debug,             # Debug Verbosity
                "new=s"             => \$opt_new_label,         # Path1
                "old=s"             => \$opt_old_label,         # Path2
                'check'             => \$opt_md5check,          # Force MD5 Check
                'diff!'             => \$opt_diff,              # Force use of diff
                "rtagid|rtag_id=s"  => \$opt_rtag_id,           # Release tag needed for release extractions
                "package=s"         => \$opt_package_name,      # Name of the package to query
                'option=s'          => \@opt_options,           # User options
                'patch:s'           => \$opt_patch,             # Generate a patch file ( for FeCru )
                );

                #
                #   UPDATE THE DOCUMENTATION AT THE END OF THIS FILE !!!
                #

#
#   Process help and manual options
#
pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result );
pod2usage(-verbose => 1)  if ($opt_help == 2 );
pod2usage(-verbose => 2)  if ($opt_help > 2 );

InitFileUtils();

#
#   Configure the error reporting process now that we have the user options
#
ErrorConfig( 'name'    => 'VCSDIFF',
             'verbose' => $opt_verbose,
             'debug'   => $opt_debug );

#
#   Sanity testing of user options
#   
Error ("Options -check and -diff cannot be combined")
    if ( $opt_md5check && $opt_diff );
Error ("Options -check and -patch cannot be combined")
    if ( $opt_md5check && defined($opt_patch) );
Error ("Options -diff and -patch cannot be combined")
    if ( $opt_diff && defined($opt_patch) );

#
#   Determine mode
#   Not all modes work on all machines
#
Verbose ("Machine Type: UNIX=$UNIX");
if ( $opt_md5check )
{
    $opt_mode = 'md5';
}
elsif ( $UNIX || $opt_diff || defined($opt_patch))
{
    $opt_mode = 'diff';
    $Name = 'diff';
    push @DiffArgs, '-r';
    $Name = 'gdiff';
    $DiffProg = LocateProgInPath( $Name, '--All');
    unless ( $DiffProg =~ m~/~ )
    {
        $Name = 'diff';
        $DiffProg = LocateProgInPath( $Name, '--All');

    }

    Error ("Cannot locate a 'diff' utility in the users PATH")
        unless ( $DiffProg =~ m~/~ );
    $DiffProg =~ tr~\\~/~;
}
else
{
    $opt_mode = 'bc2';
    $DiffWait = 1;

    #
    #   Determine the path to Beyond Compare Exe
    #       It may not be installed in the default place, but the Windows
    #       registry will know where it is
    #
    GetBeyondCompareExePath();
}

#
#   Validate user options
#
#   Be nice to the user
#   If we have two options and no labels, then assign them
#
if ( ! $opt_new_label && ! $opt_old_label )
{
    Error ("Must provide two labels on command line unless they are provided via -old and -new options")
         if ( $#ARGV < 1 );

    $opt_old_label = shift @ARGV;
    $opt_new_label = shift @ARGV;
}

Error ("Need two labels on the command line, or via options")
    unless ( $opt_old_label && $opt_new_label );

Error ("Too many command line arguments" )
    unless ( $#ARGV < 0 );

#
#   Extract parameters that will be used to create a view that is
#   unique. Will use hostname and user name
#
Error ("Machine Name not determined")
    unless ( $MACHINENAME );

Error ("USER name not determined" )
    unless ( $USER );

#
#   Need a TMP working directory
#   Used to create config files
#
Error ("TMP not found or not a directory")
    unless ( $TMP && -d $TMP );
$TMP = "$TMP/$$";

#   Validate the users patch directory
if (defined($opt_patch))
{
    # Set deafult patch file name, if none is provided
    $opt_patch = 'vcsdiff.patch' if (length $opt_patch == 0);
    my $patchDir = StripFileExt($opt_patch);
    Error ("Directory does not exist: $patchDir") if length($patchDir) > 0;
    RmDirTree ($opt_patch);
}

#
#   Translate special label names.
#
$opt_old_label = translateSpecialLabelName( "old", $opt_old_label );
$opt_new_label = translateSpecialLabelName( "new", $opt_new_label );

#
#   Create views for the two views
#   Verify that the view are present
#
Message ("Constructing views");
Message ("  old = $opt_old_label" );
Message ("  new = $opt_new_label" );
my $path1 = create_view( $opt_old_label, 1 );
my $path2 = create_view( $opt_new_label, 2 );

Error ("Cannot locate view directory: $path1" ) unless (-d $path1);
Error ("Cannot locate view directory: $path2" ) unless (-d $path2);

#
#   If one of the paths is a dynamic view and the other is a local path
#   then attempt to locate the common directories
#
if ( $#view_tags == 0 )
{
    massage_paths();
}

if ( $opt_md5check )
{
    Verbose ("Performing MD5 Check");
    my $checkfile = 'vcsdiff_md5.txt';
    my $rv;
    push @cleanFiles, $checkfile;
    $rv = JatsTool ('jats_manifest', '-quiet',
                '-manifest', $checkfile,
                '-rootdir', $path1 );

    $rv = JatsTool ('jats_manifest', '-quiet', '-check',
                '-manifest', $checkfile,
                '-rootdir', $path2 ) unless $rv;

    exit $rv;
}

if ( defined($opt_patch))
{
    Verbose ("Creating a Patch File");
    Verbose ("Diff Utility: $DiffProg");
    @DiffArgs = qw(-U10000 -uNr -x.svn -x.git);
    my $rv = System ( '--Shell', $DiffProg, @DiffArgs, split(/,/, join (',', @opt_options)), $path1, $path2, '>', $opt_patch );

    #
    #   Warn if the patch file cannot be found
    if ( -f $opt_patch) {
        Message("Created patch: $opt_patch");
    } else {
        Error ("Expected patch file not found : $opt_patch");
    }
    exit $rv;
}

#
#   Diffing the paths
#   Will use BeyondCompare under Windows
#   Will use diff under unix
#
Message ("Using '$Name' to compare two views",
         "Wait for utility to exit so that we can delete the views" ) if ($DiffWait);

Verbose ("Diff Utility: $DiffProg");
System ( $DiffProg, @DiffArgs, split(/,/, join (',', @opt_options)), $path1, $path2 );
exit 0;

#-------------------------------------------------------------------------------
# Function        : create_view
#
# Description     : Create dynamic view, based on a Vcs Tag
#
# Inputs          : $vcstag
#
# Returns         : Path to the view
#
sub create_view
{
    my ($vcstag, $num) = @_;

    #
    #   Intercept and treat the special label 'current'
    #
    return create_path_view( $vcstag )
        if ( $vcstag eq 'current'  || $vcstag =~ m~^dir=.+~ || $vcstag =~ m~^current=.+~ );

    my $tag = "${USER}_${MACHINENAME}_vcsdiff_${num}";
    push @view_tags, $tag;
        
    #
    #   Use vcsrelease to do the hard work
    #
    my @command = ( 'jats_vcsrelease',
                        '-extractfiles',
                        '-root=.' ,
                        '-noprefix',
                        '-devmode=escrow',
                        '-view', $tag ,
                        '-label', $vcstag,
                   );
    push @view_commands, \@command;

    if ( $opt_debug && -d $tag )
    {
        Message ("Using existing view");
    }
    else
    {
        JatsTool( @command );
    }

    #
    #   Minor Kludge
    #       Should be in a library
    #   Iff CC::, then process path info too
    #
    if ( $vcstag =~ m~^CC::(.*?)(::(.+))?$~ )
    {
        my $path = $1;
        if ( $path )
        {
            my $try = $tag . '/' . $path;
            if ( -d  $try )
            {
                $tag = $try;
            }
        }
    }

    return $tag;
}

#-------------------------------------------------------------------------------
# Function        : create_path_view
#
# Description     : Not using a view, using a path
#                   Return the path as requested
#
# Inputs          : $label                  - with embedded path
#
# Returns         : Path to the (dummy) view
#
sub create_path_view
{
    my ($label) = @_;
    my $path  = '.';

    $path = $1
        if ( $label =~ m~.+=(.+)~ );

    Error ("Directory not found: $path" )
        unless ( -d $path );

    $path = FullPath( $path );
    return $path;
}

#-------------------------------------------------------------------------------
# Function        : massage_paths
#
# Description     : Used when one of the paths is a view and the the other
#                   is a local directory.
#
#                   Attempt to locate the common root
#
# Inputs          : None
#
# Returns         : Modifies $path1 and $path2
#
sub massage_paths
{
    my $view_path = $view_tags[0];
    my $user_path = $path1;
    $user_path = $path2 if ( $view_path eq $path1 );

    #
    #   Split the user path into its component directory entries
    #   Start at the top and look for one of these in the view
    #
    my @user_path = split ('/', $user_path );
    shift @user_path if $user_path[0] eq '';
    my $tpath = '';
    foreach my $dir ( @user_path )
    {
        if ( -d "$view_path/$dir" )
        {
            #
            #   Common directory found
            #   Set the user path to the previous directory
            #
            $user_path = $tpath;
            if ( $view_path eq $path1   )
            {
                $path2 = $user_path;
            }
            else
            {
                $path1 = $user_path;
            }

            #
            #   now add the common part
            #
            $path1 .= "/$dir";
            $path2 .= "/$dir";
            Message ("Setting common root path ($dir)", $path1, $path2);
            last;
        }
        $tpath .= '/' if ( $tpath );
        $tpath .= $dir;
    }
}

#-------------------------------------------------------------------------------
# Function        : GetBeyondCompareExePath
#
# Description     : Determine the path to the BeyondCompare executable
#                   by looking in the Windows Registry
#
# Inputs          : None
#
# Returns         : Path to an executable
#

sub GetBeyondCompareExePath
{
    eval "require Win32::TieRegistry"
        or Error ("Win32::TieRegistry not available");

    Win32::TieRegistry::import(qw( REG_SZ REG_EXPAND_SZ REG_DWORD REG_BINARY REG_MULTI_SZ KEY_READ KEY_WRITE KEY_ALL_ACCESS ));

    my $userKey= Win32::TieRegistry->new("CUser", {Access=>KEY_READ(),Delimiter=>"/"})
        or  Error( "Can't access HKEY_CURRENT_USER key: $^E" );

    my $localKey= Win32::TieRegistry->new("LMachine", {Access=>KEY_READ(),Delimiter=>"/"})
        or  Error( "Can't access HKEY_LOCAL_MACHINE key: $^E" );

    sub checkKeys
    {
        my ($userKey, $localKey, $key) = @_;
        my ($bcKey, $DiffProg);
        $bcKey = $userKey->Open( $key );
        if ($bcKey && ($DiffProg = $bcKey->GetValue( 'ExePath' )) )
        {
            return $DiffProg;
        }
        $bcKey = $localKey->Open( $key );
        if ($bcKey && ($DiffProg = $bcKey->GetValue( 'ExePath' )) )
        {
            return $DiffProg;
        }
        return undef;
    }

    my $bcKey;
    if ($DiffProg = checkKeys( $userKey, $localKey,"Software/Scooter Software/Beyond Compare 4" ))
    {
        Verbose("Using BC4");
        push @DiffArgs, '/solo';
    }
    elsif ($DiffProg = checkKeys( $userKey, $localKey,"Software/Scooter Software/Beyond Compare 3"))
    {
        Verbose("Using BC3");
        push @DiffArgs, '/solo';
    }
    elsif ($DiffProg = checkKeys( $userKey, $localKey,"Software/Scooter Software/Beyond Compare"))
    {
        Verbose("Using BC2");
    }
    else
    {
        Error "Can't access BC2, BC3 or BC4 Keys: $^E";
    }

    Error ("BeyondCompare program not found", "Prog: $DiffProg")
        unless ( -x $DiffProg );
}

#-------------------------------------------------------------------------------
# Function        : findReleaseManagerPackage
#
# Description     : Find the package information by looking up its existing in 
#                   a given release.
#
# Inputs          : label_name
#                   label_value
#                   rtag_id
#                   package_name
#
# Returns         : 
#
sub findReleaseManagerPackage
{
    my ($label_name, $label_value) = @_;
    my (@row);
    my $found=0;

    if ( !$opt_rtag_id || !$opt_package_name )
    {
        Error( "Must specify -rtagid and -package options when the '$label_name' tag has the value '$label_value'" );
    }
    
    if ( !$package_vcs )
    {

        connectRM(\$RM_DB) unless ($RM_DB);

        # First get details from pv_id; split the package version and extension.
        my $pkg_ext;
        my $pkg_name;
        if ( $opt_package_name =~ m/(.*)\.(\w*)$/ )
        {
            $pkg_name = $1;
            $pkg_ext = $2;
        }
        else
        {
            $pkg_name = $opt_package_name;
        }

        my $m_sqlstr = "SELECT pv.PV_ID, pv.PKG_VERSION, release_manager.PK_RMAPI.return_vcs_tag(pv.PV_ID)".
                       " FROM RELEASE_MANAGER.RELEASE_CONTENT rc, RELEASE_MANAGER.PACKAGE_VERSIONS pv, RELEASE_MANAGER.PACKAGES pkg" .
                       " WHERE rc.RTAG_ID = $opt_rtag_id AND rc.PV_ID = pv.PV_ID AND pv.PKG_ID = pkg.PKG_ID AND pkg.PKG_NAME = '$pkg_name'";
        if ( $pkg_ext )
        {
            $m_sqlstr .= " AND pv.V_EXT='.$pkg_ext'";
        }
        Verbose2( $m_sqlstr );
        my $sth = $RM_DB->prepare($m_sqlstr);
        if ( defined($sth) )
        {
            if ( $sth->execute( ) )
            {
                if ( $sth->rows )
                {
                    while ( @row = $sth->fetchrow_array )
                    {
                        my $pv_id = $row[0];
                        my $ver = $row[1];
                        my $vcs = $row[2];
                        
                        Verbose( "findReleaseManagerPackage: [$opt_rtag_id, $opt_package_name] ==> [ $pv_id, $ver, $vcs]" );
                        
                        $package_name = $opt_package_name;
                        $package_ver  = $ver;
                        $package_vcs  = $vcs;
                        
                        # Most packages are released of trunk...
                        my $match = '^(SVN::.*)\/trunk::.*$';
                        if ( $package_vcs =~ m/$match/ )
                        {
                            $package_vcs_base = $1;
                            Verbose( "findReleaseManagerPackage: base on trunk ==> $package_vcs_base" );
                        }
                        else
                        {
                            # ...but some are released of branches.
                            $match = '^(SVN::.*)\/branches\/([^:]+)::.*$';
                            if ( $package_vcs =~ m/$match/ )
                            {
                                $package_vcs_base = $1;
                                $package_release_branch = $2;
                                Verbose( "findReleaseManagerPackage: base on branch $package_release_branch ==> $package_vcs_base" );
                            }
                            else
                            {
                                Error ( "Unable to determine base path from VCS: $package_vcs" );
                            }
                        }
                        $found++;
                    }
                }
                $sth->finish();
            }
        }
        else
        {
            Error( "findReleaseManagerPackage:Prepare failure" );
        }
        
        if ( $found == 0 )
        {
            Error( "No package named $opt_package_name found in the release with rtag ID $opt_rtag_id" );
        }
        elsif ( $found > 1 )
        {
            Error( "Multiple packages named $opt_package_name found in the release with rtag ID $opt_rtag_id" );
        }
    }
}

#-------------------------------------------------------------------------------
# Function        : translateSpecialLabelName
#
# Description     : Translates the name of a label (-old or -new argument) 
#                   if it is one of the special values.
#
# Inputs          : label_name
#                   label_value
#
# Returns         : new label_value
#
sub translateSpecialLabelName
{
    my ($label_name, $label_value) = @_;
    
    if ( $label_value eq "released" )
    {
        #
        #   Get the exact version released in Release Manager.
        #
        findReleaseManagerPackage( $label_name, $label_value );
        return $package_vcs;
    }
    elsif ( $label_value eq "head" )
    {
        #
        #   Get the 'head' of the released branch in release manager; this is the trunk
        #   if the package is released off trunk or a branch if the package is getting
        #   released from a branch in this release area.
        #
        findReleaseManagerPackage( $label_name, $label_value );
        if ( $package_release_branch )
        {
            return $package_vcs_base . '/branches/' . $package_release_branch;
        }
        else
        {
            return $package_vcs_base . '/trunk'
        }
    }
    elsif ( $label_value =~ m/^branch=(.*)/ )
    {
        #
        #   Get a named branch for this package.
        #
        my $branch_name = $1;
        findReleaseManagerPackage( $label_name, $label_value );
        return $package_vcs_base . '/branches/' . $branch_name;
    }
    elsif ( $label_value eq "trunk" )
    {
        #
        #   Get the true trunk of this package.
        #
        findReleaseManagerPackage( $label_name, $label_value );
        return $package_vcs_base . '/trunk';
    }
    
    return $label_value;
}


#-------------------------------------------------------------------------------
# Function        : END
#
# Description     : This function will be called as the program exits
#                   It will also be called under error conditions
#                   Close down stuff we created
#
# Inputs          : 
#
# Returns         : 
#

sub END
{
    my $exitCode = $?;

    if ( $opt_debug )
    {
        Message ("NOT Cleaning up views");
        return;
    }

    Message ("Cleaning up views") if @view_tags;
    foreach my $cmds ( @view_commands )
    {
        JatsTool( @{$cmds}, '-delete' );
    }

    foreach my $file ( @cleanFiles )
    {
        unlink $file;
    }

    #
    #   The exit code may get modified by the JatsTool
    #   Preserve any error indication
    #
    $? = $exitCode;
}

#-------------------------------------------------------------------------------
#   Documentation
#

=pod

=head1 NAME

jats_vcsdiff - Difference two views

=head1 SYNOPSIS

  jats vcsdiff [options] [old_label new_label]

 Options:
    -help               - Brief help message
    -help -help         - Detailed help message
    -man                - Full documentation
    -check              - Perform MD5SUM over both views
    -patch[=name]       - Create a patch file for the views
    -[no]diff           - Force the use of a 'diff' utility
    -option=opt1,...    - Add user options to the command line
    -old=tag            - Old VcsTag (or path or vcs keyword)
    -new=tag            - New VcsTag (or path or vcs keyword)
    -rtagid=xxx         - Specify the Release to process
    -package=xxx        - Specify the package to query from Release Manager

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message and exits.

=item B<-help -help>

Print a detailed help message with an explanation for each option.

=item B<-man>

Prints the manual page and exits.

=item B<-check>

This option controls the mode in which the program will operate.

If enabled the program will perform an MD5 Cheksum over the files in the first
view and compare that with files in the second view.

This option cannot be used in conjunction with the '-diff' option'.

=item B<-patch[=name]>

This option controls the mode in which the program will operate. If enabled the program
will generate a 'patch' file between the two views.

The patch file can be used to perform a 'Pre-commit' Code Review in tools such as Fisheye/Crucible.

If the name of the patch is not provided, the patch will be written to a faile called vcsdiff.patch otherwise
the user specified name will be used. The named patch can be created in a directory that exists.

The patch creation process will exclude directories called '.svn' and '.git'.

If using a development directory then it is recommended that all build files and artifactes be removed before the
patch is created. This can often be done with a 'jats clobber'.

=item B<-diff>

This option controls the mode in which the program will operate.

By default the program is Operating System dependent. It will:

=over 4

=item * Windows - Use Beyond Compare

=item * Unix - Use gdiff or diff

=back

This option will force the use of a 'diff' utility on both Windows and
Unix.

This option cannot be used in conjunction with the '-check' option'.

=item B<-option=opt1,..>

This option allows the user to modify the operation of the invoked diff program.

The options are passed directly to the diff utility invoved by the program. It is the users
responsibility to ensure that the options are valid.

This option may be used mutiple times, or options may be comma-seperated.

=item B<-old=tag>

This option specifies the old, or base, VcsTag for the difference report. This
tag is mandatory.

The old and new tags may be provided on the command line, or via named
options, but not both.

The tag may be of the form dir=path to force the utility to use a local
view or path.

Additional tag values are available when generating differences against packages
that are released in Release Manager.  In order to do this specific -rtagid and
-package to specify the Release Manager package version.  Once
these are specified the following tags can be used:

=over 4

=item * released

Use the current released package version in Release Manager.

=item * head

Use the Subversion HEAD of the Release Manager release path.

=item * trunk

Use the HEAD of the Subversion trunk.

=item * branch=xxx

Use the HEAD of the Subversion branch named xxx.

=back

=item B<-new=tag>

This option specifies the new, or current, VcsTag for the difference report. This
tag is mandatory.

Other than that, it has the same form as the -old tag described above.

=item B<-rtagid=xxx>

This option specified an RTAG_ID that must be determined from Release Manager.

The RTAG_ID uniquely identifies a Release. The value can be read from the URL
used to view the release. ie:

 https://auawsaweb005/ManagerSuite/Release_Manager/dependencies.asp?rtag_id=17223

=item B<-package=xxx>

Specifies the name of the package to query from Release Manager.  This can be
in the form of <package_name> or alternatly <package_name>.<ext> where <ext>
is the project-specific extension for that package.  The <package_name>.<ext>
variant is required when there is more than one package with the same name
in the release given by -rtagid.

=back

=head1 DESCRIPTION

This program has two modes of operation:

=over 4

=item 1 MD5Sum of the two views

=item 2 Invoke a differencing program.

The program that is invoked is, by default, Operating System dependent. It will:

=over 4

=item * Windows - Use Beyond Compare to perform a visual diff.

This mode simplifies the process of perform a code review between two
VCS Tags by:

=over 8

=item *

Creating a visual difference between two labels

=item *

Creating a visual difference between a label and a directory

=item *

Creating a visual difference between two directories.

=back

=item * Unix - Use gdiff or diff

=back

=back

The program will:

=over 8

=item *

Create two 'extract only' views based on the VCS Tags provided. The resultant
views are not connected to any version control system.

=item *

Perform the desired operation: MD5Sum or Difference.

=item *

Delete the created directories the comparison is complete.

=back

If one of the Vcs Tags is of the form:

=over 8

=item * current

=item * current=path

=item * dir=path

=back

Then the tag will be treated as a directory and will be used for one side
of the comparison.

If a Vcs Tag is of the form:

=over 8

=item * released

=item * head

=item * trunk

=item * branch=xxx

=back

Then the source code is checked out of source control based on a Release Manager
rtagid (-rtagid option) and package name (-package) option.

Two directories views will be created. These should be deleted by this program,
but may remain if the command line program is terminated.

=head1 EXAMPLE

The following command will compare a Subversion view with a ClearCase view.

    jats vcsdiff SVN::AUPERASVN01/COTS/crc/tags/crc_26.4.0007.cr@18587 CC::/MASS_Dev_Infra/crc::crc_26.4.0006.cr -check

The following command will compare a Subversion View with a local directory

    jats vcsdiff SVN::AUPERASVN01/COTS/crc/tags/crc_26.4.0000.cr dir=crc

The following command will compare the release version of the ct-spa package from Pulse 2.7.0 with the current head of that package

    jats vcsdiff -rtagid=38970 -package=ct-spa released head

The following command will create a 'patch' file between the specified version of the crc package and the version in the 
directory named 'crc'.

    ats vcsdiff SVN::AUPERASVN01/COTS/crc/tags/crc_26.4.0000.cr dir=crc -patch

=cut