vvdiff - Recursive Diff Script For ClearCase

M. Mitchell


Introduction

Scenario: You are using ClearCase for configuration management in Linux, Unix, Windows or Mac OSX, and you want to graphically compare files and directories in your current view with those in another view.
Solution: Use this vvdiff script to quickly and easily compare one file, one directory, or even a directory tree.

If you ever wanted to graphically compare, say, a thousand files in two different ClearCase views, then you might want to try the vvdiff script (in either bash or perl form). On an ancient Sun computer, 2400 files are compared in about two minutes.

Hints:

Before you can use vvdiff, you must install a generic open source software diff tool such as xxdiff or meld.

As of March 2006, similar scripts for CVS and Subversion using xxdiff are available at Helper Scripts for xxdiff. Thanks Martin!

The vvdiff Script For ClearCase

The vvdiff script comes in bash and perl versions. Both versions use the either of two excellent diff tools (xxdiff or meld), with ClearCase. Copy vvdiff to some directory in your path (as shown by 'echo $PATH').

Bash version of vvdiff

#!/bin/bash

## vvdiff: Graphically compares a file or directory in two ClearCase views
## author: Mark Mitchell (http://home.earthlink.net/~mmc1919)
## requirements: xxdiff (http://xxdiff.sourceforge.net) or meld (http://meld.sourceforge.net/)
##               bash shell
## options: documented in usage below and available by specifying "-h"

RENDEZVOUS_DIR="$HOME/.vvdiff_rndzvs"

## parse command line
OPTS=""
BAD=""
let i=1; # i=0 is vvdiff itself
let arg=0;
while [ -n "${!i}" ]; do
    case "${!i}" in
	"--help" | "-h") 
	    BAD="yes";;
	"--exclude" | "-e")
	    OPTS=$OPTS"${!i} "; let i=i+1; OPTS=$OPTS"${!i} ";;
	"--exit-on-same" | "-D" |\
	"--ignore-all-space" | "-w" |\
	"--ignore-blank-lines" | "-B" |\
	"--ignore-case" | "-i" |\
	"--ignore-space-change" | "-b" |\
	"--recursive" | "-r")
	    OPTS=$OPTS"${!i} ";;
	*) if [ "${!i:0:1}" == "-" ]; then 
	    BAD="yes";
	   else
            let arg=arg+1;
	    ARGS[$arg]="${!i}";
	   fi;;
    esac;
    let i=i+1;
done

if test "$arg" -ne "2" -o -n "$BAD" -o ! -a "${ARGS[1]}" ; then
    echo "Usage: vvdiff [OPTIONS]  file | directory   other_view"
    echo ""
    echo "Graphically compares a file or directory in one ClearCase view to another"
    echo ""
    echo "OPTIONS are:"
    echo "--exclude, -e pattern      Skip files whose basenames match pattern"
    echo "--exit-on-same, -D         Exit quietly if there are no differences"
    echo "--help, -h                 Display this help"
    echo "--ignore-all-space, -w     Ignore white space when comparing lines"
    echo "--ignore-blank-lines, -B   Ignore blank lines"
    echo "--ignore-case, -i          Treat upper and lower case as identical"
    echo "--ignore-space-change, -b  Ignore white space when comparing lines"
    echo "--recursive, -r            List subdirectories recursively"
else

    ## clean up rendezvous directory
    if [ -a $RENDEZVOUS_DIR ]; then
	rm -rf $RENDEZVOUS_DIR/*;
    else
	mkdir $RENDEZVOUS_DIR;
    fi

    ## copy from other view to rendezvous directory
    echo "Comparing"`find ${ARGS[1]} | wc -l`" files"
    cleartool setview -exec "cp -r ${ARGS[1]} $RENDEZVOUS_DIR" ${ARGS[2]};

    ## diff current view with other view. Replace xxdiff by meld if desired
    xxdiff $OPTS ${ARGS[1]} $RENDEZVOUS_DIR/`basename ${ARGS[1]}`
fi

Perl version of vvdiff

#!/usr/bin/perl

# vvdiff: Graphically compares a file or directory in two ClearCase views
# hints: Try using a wrapper (such as vvdiff_filtered) that uses several '--exclude' filters
#        to strip out unimportant files for a huge increase in speed. Removing object files, 
#        libraries and executables is highly recommended
# author: Mark Mitchell (http://home.earthlink.net/~mmc1919)
# requirements: xxdiff (http://xxdiff.sourceforge.net) or meld (http://meld.sourceforge.net)
#               perl
# options: documented in usage below and available by specifying "-h"
# changes: 8/22/2005 m. mitchell replaced "cp -r" with "find" command so filtering
#        would be performed before, rather than after, the copy. This required consolidating
#        the copy commands into a new batch file so cleartool would still be invoked only
#        once (rather than once per copied file), since each invocation seemed to be very slow
#          9/6/2005 m. mitchell fixed problem with copying single file or directory
#        from a directory other than the current directory

my($rendezvousDir);
$rendezvousDir=$ENV{'HOME'} . '/.vvdiff_rndzvs';
$batchScript=$ENV{'HOME'} . '/.vvdiff_batch'; # consolidate copies into single script

# parse command line
my($opts)='';
my($isBad)=0;
my($argCount)=0;
my($isSecondArgument)=0; # true if next argument is a second argument to be passed through
my($isExcludeArgument)=0; # true if next argument is the argument to an --exclude
my($excludeFindFilters)="";

foreach $argnum (0 .. $#ARGV) {
    my($arg)=$ARGV[$argnum];

  CASE: {
      (($arg eq "--help") || ($arg eq "-h")) && do {
	  $isBad=1;
	  last CASE;
      };
      (($arg eq "--exclude") || ($arg eq "-e")) && do {
	  $opts=$opts . $arg . " ";
	  $isSecondArgument=1;
          $isExcludeArgument=1; # second argument that is also argument for --exclude
	  last CASE;
      };
      ($isSecondArgument ||
       ($arg eq "--exit-on-same") || ($arg eq "-D") ||
       ($arg eq "--ignore-all-space") || ($arg eq "-w") ||
       ($arg eq "--ignore-blank-lines") || ($arg eq "-B") ||
       ($arg eq "--ignore-case") || ($arg eq "-i") ||
       ($arg eq "--ignore-space-change") || ($arg eq "-b") ||
       ($arg eq "--recursive") || ($arg eq "-r")) && do {
	   $opts=$opts . $arg . " ";
	   $isSecondArgument=0;
           if ($isExcludeArgument eq 1) {
             if ($excludeFindFilters ne "") {
               $excludeFindFilters = $excludeFindFilters . " -a"; # prepend conjunction since not first
             }
             $excludeFindFilters = $excludeFindFilters . " ! -name '" . $arg . "'";
             $isExcludeArgument=0;
           }
	   last CASE;
       };
      do {
	  if ('-' eq substr($arg,0,1)) {
	      $isBad=1;
	  }
	  else {
	      $argCount=$argCount+1;
	      $args[$argCount]=$arg;
	  }
      };
  }
}

if ((2 != $argCount) || $isBad || (! -e $args[1])) {
    print "Usage: vvdiff [OPTIONS]  file | directory  other_view\n";
    print "\n";
    print "Graphically compares a file or directory in one ClearCase view to another\n";
    print "\n";
    print "OPTIONS are:\n";
    print "--exclude, -e pattern      Skip files whose basenames match pattern\n";
    print "--exit-on-same, -D         Exit quietly if there are no differences\n";
    print "--help, -h                 Display this help\n";
    print "--ignore-all-space, -w     Ignore white space when comparing lines\n";
    print "--ignore-blank-lines, -B   Ignore blank lines\n";
    print "--ignore-case, -i          Treat upper and lower case as identical\n";
    print "--ignore-space-change, -b  Ignore white space when comparing lines\n";
    print "--recursive, -r            List subdirectories recursively\n";
}
else {
    # clean up rendezvous directory
    use File::Find;
    finddepth (sub {
	if (-f $_) {
	    unlink $_;
	} else {
	    rmdir $_;
	}
    }, $rendezvousDir);

    # clean up trailing slash in first argument for dirname and basename
    use File::Basename;
    if (length($args[1]) > 1) { 
      if (substr($args[1],length($args[1])-1,1) eq '/') { 
        chop($args[1]); 
      } 
    }

    # build find command using appropriate filtering
    $fileCount=0;
    my($excludeFindStartingPoint)="find $args[1]";
    $excludeFindCmd = $excludeFindStartingPoint . $excludeFindFilters;

    # create batch script from the filtered list of files, of the OTHER view
    use File::Path;
    my($cleartoolCmd)='cleartool setview -exec "'.$excludeFindCmd.'" '.$args[2]."\n";
    open(FIND_OUT, $cleartoolCmd . "|") or die("Could not execute unix find command");
    open(BATCH_SCRIPT, ">$batchScript") or die("Could not open batch script");
    while(<FIND_OUT>) {
      $sourcePath = $_;
      chop($sourcePath);
      $targetPath = $rendezvousDir.'/'.$sourcePath;
      if (-f $sourcePath) {
        # mirror directory from other view if necessary
        $targetDir = dirname($targetPath);
        if (! -d $targetDir) {
          mkpath("$targetDir");
        }

        # copy file from other view to rendezvous directory. Use single quotes to allow spaces,
        # and redirect stderr to prevent unimportant "cp: cannot access" errors for non-vobs objects
        $copyCmd = "cp '$sourcePath' '$targetPath' 2>/dev/null;\n";
        print BATCH_SCRIPT $copyCmd;
      } else {
        # mirror directory from other view
        mkpath("$targetPath");
      }

      $fileCount=$fileCount+1;
    }
    close(FIND_OUT);
    close(BATCH_SCRIPT);

    # display file count so user:
    #   -knows how long to wait
    #   -knows if filtering should be improved
    print "Comparing " . $fileCount . " files\n";

    # copy from the other view to the rendezvous directory using cleartool
    system("chmod +x $batchScript");
    $cleartoolCmd='cleartool setview -exec "'.$batchScript.'" '.$args[2]."\n";
    system($cleartoolCmd);

    # diff current view with files from the other view. Replace xxdiff by meld
    # if desired
    $cmd='xxdiff '.$opts.'\''.$args[1].'\' '.$rendezvousDir.'/'.$args[1]."\n";
    system($cmd);
}

Usage

There are two required arguments to vvdiff. The first required argument is the file or directory that you want to compare. The second required argument is a ClearCase view. The file(s) in the current view will be compared to the same file(s) in the other view.

For example, in the following example the xxdiff-3.0.1 directory in the current view is compared with the same directory in the Release view:

vvdiff xxdiff-3.0.1 Release

This produces the following picture. Double clicking on any file will bring up a new window with the diff output for that file. Double clicking on any directory will bring up a new window with the diff output for that directory.

xxdiff picture

Optional command line arguments can be listed by specifying the "-h" option.