#!/usr/local/bin/perl # # cdiff -- Do a cleardiff with lines of context # # Version 1.0.11 last modified 10/25/96 by Brad Appleton # Created 03/10/95 by Brad Appleton # #------------------------------------------------------------------------- # COPYING # ======= # This file/program is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. Please refer to the # license that came with your Perl distribution for more details. # # DISCLAIMER # =========== # This software is distributed in the hope that it will be useful, but # is provided "AS IS" WITHOUT WARRANTY OF ANY KIND, either expressed or # implied, INCLUDING, without limitation, the implied warranties of # MERCHANTABILITY and FITNESS FOR A PARTICULAR PURPOSE. # # The ENTIRE RISK as to the quality and performance of the software # IS WITH YOU (the holder of the software). Should the software prove # defective, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR # CORRECTION. # # IN NO EVENT WILL ANY COPYRIGHT HOLDER OR ANY OTHER PARTY WHO MAY CREATE, # MODIFY, OR DISTRIBUTE THE SOFTWARE BE LIABLE OR RESPONSIBLE TO YOU OR TO # ANY OTHER ENTITY FOR ANY KIND OF DAMAGES (no matter how awful - not even # if they arise from known or unknown flaws in the software). ########################################################################## #------------------------------------------------------------------------- # BEGIN CONFIGURATION SECTION # # (dont forget to change the '#!' line at the top of the file to # use the appropriate path/invocation for Perl on your system) #------------------------------------------------------------------------- # You *must* have Johan Vromans "newgetopt.pl" option parsing package # for Perl (version 1.14 or later) installed as part of your standard # perl library (it is in the Perl5 distribution, but not in all Perl4 # distributions). require 'newgetopt.pl'; # The path to the Atria installation area $ATRIAHOME = '/usr/atria'; # The path to the directory of executable ClearCase commands $CLEARBIN = "${ATRIAHOME}/bin"; # The path to the 'cleartool' command $CLEARTOOL = "${CLEARBIN}/cleartool"; # The extended version naming suffix to use. $CLEARCASE_XN_SFX = "\@\@"; # In order to use the '-fmt patch' feature of cdiff, you will need a # program on your system that does a file comparison displaying lines # of context in a format that is accepted by Larry Wall's "patch" # program. Most versions of diff(1) take a '-c' or '-C' option to do # this (not all of them will allow you to specify the number of lines # of context). You will need to set the definition of the PATCHDIFF # variable to the appropriate command-line invocation (note that a # "%d" will be replaced with the desired number of lines of context # to display). $PATCHDIFF = 'diff -C %d'; #------------------------------------------------------------------------- # END CONFIGURATION SECTION #------------------------------------------------------------------------- ## Get basename and dirname of program path ($NAME, $THISDIR) = ($0 =~ m|^(.*)/([^/]+)$|o) ? ($2, $1) : ($0, '.'); $SYNOPSIS = "$NAME [-Help] [-Debug] [-after NUMBER] [-before NUMBER] [-blank_ignore] [-fmt FORMAT] [-linenumbers] [-nocontext] [-noheaders] [-noprolog] [-pred] [-unlimited] XPNAME ..."; ### Some constants that the user can override from the command-line %FMT = ('patch', 0, 'deleted', "<", 'inserted', ">", 'common', "", 'separator', "", 'lheader', "-----", 'rheader', "-----", 'divider', "********************************", 'base', "<<<", 'contrib', ">>>", 'numwidth', 4, 'reverse', 0); $ARGUMENTS = " -Help Print this help message and exit. -Debug Enable debugging output. -after NUMBER Number of lines of context to show after a difference. -before NUMBER Number of lines of context to show before a difference. -unlimited Show unlimited lines of context (all common lines). -blank_ignore Pass the '-blank_ignore' option to 'cleardiff'. -linenumbers Include line numbers (for each file) in the output. -pred Pass the '-pred' option to 'cleardiff'. -nocontext Don't print lines of context. -noheaders Don't include change headers in the output. -noprolog Don't print the 'legend' prolog at the beginning that shows which file names correspond to which file numbers. -fmt FORMAT Specify output format attributes. Valid FORMAT values are: 'patch' Specify that output should be in the context-diff format used by Larry Wall's patch program. Causes all other formats to be ignored (Note: requires \"$PATCHDIFF\"). 'deleted=PREFIX' Specify the prefix for a deleted line (default = \"$FMT{'deleted'}\") 'inserted=PREFIX' Specify the prefix for an inserted line (default = \"$FMT{'inserted'}\") 'common=PREFIX' Specify the prefix for a line of context that is common to all files (default = \"$FMT{'common'}\") 'numwidth=NUMBER' Specify the width of any line numbers that are to appear in the output (for use with the '-linenumbers' option). NUMBER must be a positive integer (default = $FMT{'numwidth'}) 'reverse' Specify that line numbers should be printed in reverse order (for use with the '-linenumbers' option). By default line numbers are printed left-to-right in the order: file 1, file 2, ..., file N Specifying 'reverse' causes them to be printed in reverse order as in: file N, ..., file 2, file 1 'separator=STRING' Specify the text to separate a deleted/inserted/common prefix from the actual source (default = \"$FMT{'separator'}\") 'lheader=PREFIX' Specify the prefix (left-header) for header-lines that indicate a change was made (default = \"$FMT{'lheader'}\") 'rheader=SUFFIX' Specify the suffix (right-header) for header-lines that indicate a change was made (default = \"$FMT{'rheader'}\") 'header=STRING' Specify the suffix+prefix for header-lines that indicate a change was made 'divider=STRING' Specify the contents of the dividing line that separates a section of context from the following/preceding section (default = \"$FMT{'divider'}\") 'base=PREFIX' Specify the prefix for the name of the base-file in the prologue section (default = \"$FMT{'base'}\") 'contrib=PREFIX' Specify the prefix for the name of a contributor-file in the prologue section (default = \"$FMT{'contrib'}\") FORMAT keywords may be abbreviated to a unique prefix and are case insensitive. This option may be specified multiple times. XPNAME Extended pathname to the desired element in your view (the version path may be abbreviated). Option names are case insensitive and only a unique prefix is required. "; $DESCRIPTION = " $NAME will produce a serial-format difference listing with surrounding lines of context for the named files. A filename may be specified as a version-extended pathname (abbreviated or otherwise), or simply by a version selector (in which case the corresponding element is assumed to be the same as the element associated with the previously specified filename). Exit status is 0 if there are no differences to report, 1 if there are, and 2 if an error occurred. "; ##---------------------------------------------------------------------- ## FUNCTION: ## ErrorMsg -- Print an error message (prefixed by "$NAME: "). ## ## SYNOPSIS: ## &ErrorMsg("message text"); ## ## ARGUMENTS: ## A single string containing the message text. ## ## RETURN VALUE: None. ## ## PRECONDITIONS: ## The global variable $NAME should be set to the name of the running ## script. ## ## SIDE EFFECTS: ## - Prints to STDERR ##---------------------------------------------------------------------- sub ErrorMsg { print STDERR ("${NAME}: ", @_, "\n"); } ##---------------------------------------------------------------------- ## FUNCTION: ## FatalMsg -- Print an error message and then abort ## ## SYNOPSIS: ## &FatalMsg("message text"); ## ## ARGUMENTS: ## A single string containing the message text. ## ## RETURN VALUE: None. ## ## PRECONDITIONS: ## The global variable $NAME should be set to the name of the running ## script. ## ## SIDE EFFECTS: ## - Prints an error message ## - Exits the script using exit(2) ##---------------------------------------------------------------------- sub FatalMsg { &ErrorMsg(@_); exit(2); } ##---------------------------------------------------------------------- ## FUNCTION: ## Usage -- Print a Usage message and then exit with the specified ## exit-value. If the exit-value is > 1, then usage is terse ## (synopsis only) and goes to STDERR. Otherwise, usage is ## verbose and goes to STDOUT. ## ## SYNOPSIS: ## &Usage([$val]); ## ## ARGUMENTS: ## $val : (optional) The integer exit value to use (defaults to 2). ## ## RETURN VALUE: None. ## ## PRECONDITIONS: ## - The global variable $NAME should be set to the name of the ## running script. ## - The global variable $SYNOPSIS should be a string containg the ## command-line invocation synopsis of the running script (like ## in a standard Unix manual page). ## - The global variable $ARGUMENTS should be a string containing a ## description of each command-line option and argument listed ## in the SYNOPSIS (like in a standard Unix manual page). ## ## SIDE EFFECTS: ## Exits the script using exit with the given exit-status. ##---------------------------------------------------------------------- sub Usage { local($_) = @_; $_ = 2 unless (/^\d+$/o); select STDERR unless ($_ <= 1); print "\nUsage: ${SYNOPSIS}\n"; print "\nArguments:${ARGUMENTS}${DESCRIPTION}\n" unless ($_ > 1); exit($_); } ##---------------------------------------------------------------------- ## FUNCTION: ## DbgPrint -- Print debugging output. Prints the specified arguments ## only if '-debug' was specified on the command-line. ## ## SYNOPSIS: ## &DbgPrint(">debug message text\n"); ## ## ARGUMENTS: ## The string containing the debug message text (a newline is not ## automatically printed so include it yourself if you need it). ## ## RETURN VALUE: None. ## ## PRECONDITIONS: ## Assumes that the global variable named $opt_debug will evaluate to ## TRUE if '-debug' was given on the command-line. ## ## SIDE EFFECTS: ## Prints to STDERR ##---------------------------------------------------------------------- sub DbgPrint { print STDERR (@_) if ($opt_debug); } #################################################################### ## The following global variables are *very* important: ## ----------------------------------------------------------------- ## The text of the current difference as reported by cleardiff $DIFFERENCE = ''; ## The type of the current difference $Changed = 'changed'; $ChangedFrom = 'changed from'; $ChangedTo = 'changed to'; $Deleted = 'deleted'; $Inserted = 'inserted'; $MovedFrom = 'deleted/moved'; $MovedTo = 'inserted/moved'; $EndOfDiff = 'end of differences'; ## indicator for end-of-diff! ## Save the original file-list passed in and parse the new one from the ## beginning of the cleardiff-header. @FILES = (); ## Keep an array of filehandles where $HANDLES[$i] is the filehandle used ## for file #(i-1). If it exists, it means the file is open. Since ## cleardiff starts file numbers at '1', we will keep the name of file ## number N in $FILES[N - 1]. @HANDLES = (); ## Keep an array of line numbers where $LINES[$i] is the line-number of ## file #(i-1) that was last displayed as part of the output of the ## context-diff. @LINES = (); #################################################################### ## Get the file index from the file spec. The file-spec is of the ## format "file N" where 1 <= N <= 32. sub FileIndex { local($_) = @_; if (/^file\s+(\d+)$/o) { return ($1 - 1); } return undef; } ## Get the line range from the given spec. It may be a single number, or a ## range as in 5-10. In either case, return the pair ($begin, $end) sub LineRange { local($_) = @_; local(@range); local($begin, $end); if (/^\d+$/o) { push(@range, $_, $_); ## single number, begin == end } elsif (/^(\d+)-(\d+)$/o) { push(@range, $1, $2); } @range; } ## Print prologue for the file differences. The prolog consists of ## the initial header that shows the names of all files compared sub PrintPrologue { local($_); local($i); unless ($opt_noprologue) { print "$FMT{'divider'}\n"; for ($i = 0; $i <= $#FILES; ++$i) { local($prefixKey) = ($i == 0) ? 'base' : 'contrib'; printf("%s file %d: %s\n", $FMT{$prefixKey}, $i + 1, @FILES[$i]); } print "$FMT{'divider'}\n"; } } ## Print a change header with the given text sub PrintChangeHeader { local($_) = @_; $_ = "[$_]" if ($_); print "$FMT{'lheader'}$_$FMT{'rheader'}\n" unless ($opt_noheaders); } ## Format the line number(s) for this line. Returns a string with all the ## required line numbers printed in the appropriate columns. sub FormatLineNumbers { local($fileIndex, $prefixKey) = @_; local(@lineNumbers); local($i); local($_); ## If this is a context line, we need to print line numbers for all files ## in each column. Otherwise, we need to print only the line number for ## the current file (but still in the appropriate column) local($isContext) = ($prefixKey eq 'common'); for ($i = 0; $i <= $#FILES; ++$i) { if ($LINES[$i] && ($isContext || ($i == $fileIndex))) { push(@lineNumbers, sprintf("%$FMT{'numwidth'}d", $LINES[$i])); } else { push(@lineNumbers, sprintf("%$FMT{'numwidth'}s", " ")); } } @lineNumbers = (reverse @lineNumbers) if ($FMT{'reverse'}); $_ = join(" ", @lineNumbers) . " "; $_; } ## Print the given line with the appropriate decorations sub PrintSrcLine { local($srcLineText, $fileIndex, $prefixKey) = @_; local($_) = "$FMT{$prefixKey} "; $_ .= &FormatLineNumbers($fileIndex, $prefixKey) if ($opt_linenumbers); $_ .= "$FMT{'separator'} " if ($FMT{'separator'}); $_ .= $srcLineText; print "$_"; } ## Read a line from the file corresponding to the given index, update its ## current line number, and return the line that was read. Open the file ## for reading if it wasnt already open! sub GetLine { local($fileIndex) = @_; local($fname) = $FILES[$fileIndex]; local($fhandle); local($_); ## Get the corresponding handle (opening the file if necessary); if ($HANDLES[$fileIndex]) { $fhandle = $HANDLES[$fileIndex]; } else { $fhandle = $fname; $fhandle =~ s/\W/_/go; open($fhandle, $fname) || &FatalMsg("Can't open $fname for reading: $!"); $HANDLES[$fileIndex] = $fhandle; $LINES[$fileIndex] = 0; } $_ = <$fhandle>; ++$LINES[$fileIndex] if ($_); $_; } ## Print the given line range of the given file ## usage: &PrintSrcRange($fileIndex, $start, $end, $prefixKey); ## $fileIndex is the index in @FILES ## $start may be a number, or '.' for the current line position, ## or '. + N' for N lines past the current position. ## $end may be a number or '$' for EOF or ''. + N' for N lines ## past the current position ## $prefix is the key in %FMT of the prefix for the type of lines we ## are printing (deleted, inserted, or common) ## sub PrintSrcRange { local($fileIndex, $start, $end, $prefixKey) = @_; local($i); local($_); ## Interpret any occurrences of '.' in the line numbers local($dot) = $LINES[$fileIndex] + 1; if ($start =~ /\./o) { $start =~ s/\./${dot}/go; eval "\$start = $start"; } if ($end =~ /\./o) { $end =~ s/\./${dot}/go; eval "\$end = $end"; } ## Print the desired range (if we havent already passed it) return if (($end ne '$') && ($LINES[$fileIndex] >= $end)); local($isContext) = ($prefixKey eq 'common'); while ($_ = &GetLine($fileIndex)) { ## If this is a line of context then we need to keep all the other ## current line numbers from all the other files in sync. if ($isContext) { for ($i = 0; $i <= $#FILES; ++$i) { &GetLine($i) unless ($i == $fileIndex); } } next if ($LINES[$fileIndex] < $start); &PrintSrcLine($_, $fileIndex, $prefixKey); last if (($end ne '$') && ($LINES[$fileIndex] == $end)); } } ## Print the "before" and "after" lines of context for the given range sub PrintContext { local($fileIndex, $begin, $end, $after) = @_; local($current, $preContext, $postContext); local($needSeparator); ## Actually - we wait to print post-context until its time to ## print the next pre-context. This way it is easier to tell if ## the contexts would overlap, or if the next change will be ## part of the previous post-context. if ($LINES[0] > 0) { ## Make sure trailing context doesnt overlap with this range $current = $LINES[0] + 1; $postContext = ($current + $opt_after) - 1; if ($opt_unlimited || (($fileIndex == 0) && ($after < $postContext))) { $postContext = $after - 1; } &PrintSrcRange(0, $current, $postContext, 'common'); ++$needSeparator; } $preContext = ($after - $opt_before) + 1; $preContext = 1 if ($opt_unlimited && (! $LINES[0])); if (($preContext > ($LINES[0] + 1)) && $needSeparator) { print "$FMT{'divider'}\n"; } &PrintSrcRange(0, $preContext, $after, 'common'); } ## Print the given changed line-range of the given file with lines of context ## ## usage: &PrintDifference($range, $file, $ref); ## where ## - $range is the line-range to display ## - $file is the file to use ## - $ref is a line (or line-range) indicating where the lines ## to be displayed are with respect to 'file 1'. If $ref is ## omitted, then $file is assumed to be 'file 1'. sub PrintDifference { local($action, $range, $file, $ref) = @_; local($begin, $end) = &LineRange($range); $end = $begin unless ($end); local($fileIndex) = &FileIndex($file); local($after) = $begin - 1; if ($ref) { local(@ary) = &LineRange($ref); $after = pop(@ary) if (@ary > 0); } &PrintContext(0, $begin, $end, $after) unless ($opt_nocontext || ($action eq $ChangedTo)); local($prefixKey); local($_) = $action; if (/delete/o) { $prefixKey = 'deleted'; } elsif (/insert/o) { $prefixKey = 'inserted'; } elsif ($action eq $ChangedFrom) { $prefixKey = 'deleted'; $_ = 'changed'; } elsif ($action eq $ChangedTo) { $prefixKey = 'inserted'; $_ = 'changed'; } ## Print lines $begin through $end of $file local($text) = "$DIFFERENCE" unless ($action eq $ChangedTo); &PrintChangeHeader($text); &PrintSrcRange($fileIndex, $begin, $end, $prefixKey); &PrintChangeHeader("end of $_ lines") unless ($action eq $ChangedFrom); } ## Cleanup -- print final context and close all the files we left open sub Cleanup { local($_); local($offset) = $opt_after - 1; ## Print trailing 'file 1' context unless ($opt_nocontext) { local($postContext) = ($opt_unlimited) ? '$' : ". + $offset"; &PrintSrcRange(0, ".", $postContext, 'common') if ($LINES[0] > 0); } ## Close all files for (@HANDLES) { close($_); } } ## Given a diff-header explaining a change that was made, figure out ## the type of change and then print it. sub ParseDifference { local($_) = @_; &DbgPrint("[DEBUG] : -----[$_]-----\n"); ## globally remember the current difference description $DIFFERENCE = $_; ## Abbreviations used: ## lr : line range ## fn : file name ## ln : line number local($lr, $fn, $ln) = ('\d+-?\d*', 'file\s+\d+', '\d+'); local($action, $srcRef, $srcFile, $destRef, $destFile); ## See what kind of difference this is ... if (/^($EndOfDiff)$/o) { $action = $1; &Cleanup(); } elsif (/^($lr)\s*($fn)? ($Changed) to ($lr)\s*($fn)?$/o) { ($srcRef, $srcFile, $action, $destRef, $destFile) = ($1, $2, $3, $4, $5); $srcFile = 'file 1' unless ($srcFile); $destFile = 'file 2' unless ($destFile); &PrintDifference($ChangedFrom, $srcRef, $srcFile); &PrintDifference($ChangedTo, $destRef, $destFile, $srcRef); } elsif (/^($Deleted) ($lr)\s*($fn)? after ($ln)\s*($fn)?$/o) { ($action, $srcRef, $srcFile, $destRef, $destFile) = ($1, $2, $3, $4, $5); $srcFile = 'file 1' unless ($srcFile); $destFile = 'file 2' unless ($destFile); &PrintDifference($action, $srcRef, $srcFile); } elsif (/^after ($ln)\s*($fn)? ($Inserted) ($lr)\s*($fn)?$/o) { ($srcRef, $srcFile, $action, $destRef, $destFile) = ($1, $2, $3, $4, $5); $srcFile = 'file 1' unless ($srcFile); $destFile = 'file 2' unless ($destFile); &PrintDifference($action, $destRef, $destFile, $srcRef); } elsif (/^($MovedFrom) ($lr)\s*($fn)? after ($ln) \(now at ($lr)\)\s*($fn)?$/o) { ($action, $srcRef, $srcFile, $destRef, $destFile) = ($1, $2, $3, $4, $6); $srcFile = 'file 1' unless ($srcFile); $destFile = 'file 2' unless ($destFile); &PrintDifference($action, $srcRef, $srcFile); } elsif (/^after ($ln)\s*($fn)? ($MovedTo) ($lr) \(was at ($lr)\)\s*($fn)?$/o) { ($srcRef, $srcFile, $action, $destRef, $destFile) = ($1, $2, $3, $4, $6); $srcFile = 'file 1' unless ($srcFile); $destFile = 'file 2' unless ($destFile); &PrintDifference($action, $destRef, $destFile, $srcRef); } else { &FatalMsg("Unknown difference description \"$_\"."); } } ## Perform a context-diff in 'patch' format for the given files sub PatchDiff { local(@files) = @_; local($num) = ($opt_before > $opt_after) ? $opt_before : $opt_after; local($_) = sprintf($PATCHDIFF, $num) . join(" ", @files); &FatalMsg("Can't use 'patch' format for more than 2 files") if (@files > 2); system($_); exit($? / 256); } ## Perform a context-diff for the given files sub ContextDiff { local(@files) = @_; local($_); ## Keep track of the number of "changes" we see local($numChanges) = 0; ## Construct the cleardiff command and read the output local($diffCmd) = "$CLEARBIN/cleardiff"; $diffCmd = "$CLEARTOOL diff" if ($opt_predecessor); $diffCmd .= " -serial_format"; if ($opt_predecessor) { $diffCmd .= ' -predecessor'; $diffCmd .= ' -options "-headers_only'; $diffCmd .= ' -blank_ignore' if ($opt_blank_ignore); $diffCmd .= '"'; } else { $diffCmd .= ' -headers_only'; $diffCmd .= ' -blank_ignore' if ($opt_blank_ignore); } $diffCmd .= ' ' . join(' ', @files); open(CLEARDIFF, "$diffCmd | ") || &FatalMsg("Can't run '$diffCmd': $!"); while () { chop; next if /^\*+$/o; if (/^\s*Files are identical\s*$/io) { &ErrorMsg($_); } elsif (/^(<<<|>>>)\s+file\s+(\d+):\s+(.*)$/o) { local($fileNum, $fileName) = ($2, $3); push(@FILES, $fileName); if ($fileNum != @FILES) { &FatalMsg("Unexpected file order in diff header."); } } elsif (/^\-+\[(.+)\]\-+$/o) { &PatchDiff(@FILES) if ($FMT{'patch'}); &PrintPrologue unless ($numChanges++); &ParseDifference($1); } else { &FatalMsg("Unexpected line in diff output:\n\t$_"); } } close(CLEARDIFF); ## Don't forget to cleanup &ParseDifference($EndOfDiff) if ($numChanges); ($numChanges > 0); } ## Match a string against a set of keywords (case insensitive and ## allowing unique prefixes). Return the list of matching elements. sub MatchKeyword { local($str, @keywords) = @_; local($_, @matches); @matches = grep(m/^${str}$/i, @keywords); @matches = grep(m/^${str}/i, @keywords) if (@matches == 0); @matches; } ## Return the maximum of a list of numbers sub Max { local(@nums) = @_; local($max) = shift(@nums); local($_); for (@nums) { $max = $_ if ($_ > $max); } $max; } ## Set up and verify the specified formats sub SetupFormats { local(@fmtSpecs) = @_; local($_); local($errors); local(@matches); foreach (@fmtSpecs) { local($name, $text) = ($_, 1); ($name, $text) = ($1, $2) if (/^([^=]*)=(.*)$/o); @matches = &MatchKeyword($name, 'header', (keys %FMT)); if (@matches == 1) { $name = shift(@matches); if ($name eq 'header') { $FMT{'lheader'} = $FMT{'rheader'} = $text; } else { $FMT{$name} = $text; } } elsif (@matches == 0) { &ErrorMsg("invalid format specifier \"$_\""); ++$errors; } else { &ErrorMsg("ambiguous format specifier \"$_\""); ++$errors; } } ## Now make sure that $FMT{'deleted'}, $FMT{'inserted'}, ## and $FMT{'common'} are all the same length (padding them ## on the right with spaces if necessary) local($delLen, $insLen, $comLen) = (length($FMT{'deleted'}), length($FMT{'inserted'}), length($FMT{'common'})); local($maxlen) = &Max($delLen, $insLen, $comLen); $FMT{'deleted'} = sprintf("%-${maxlen}s", $FMT{'deleted'}) if ($delLen < $maxlen); $FMT{'inserted'} = sprintf("%-${maxlen}s", $FMT{'inserted'}) if ($insLen < $maxlen); $FMT{'common'} = sprintf("%-${maxlen}s", $FMT{'common'}) if ($comLen < $maxlen); ## Now do the same for $FMT{'base'} and $FMT{'contrib'} local($baseLen, $contribLen) = (length($FMT{'base'}), length($FMT{'contrib'})); $maxlen = &Max($baseLen, $contribLen); $FMT{'base'} = sprintf("%-${maxlen}s", $FMT{'base'}) if ($baseLen < $maxlen); $FMT{'contrib'} = sprintf("%-${maxlen}s", $FMT{'contrib'}) if ($contribLen < $maxlen); (! $errors); } ## Parse options and check for help $rc = &NGetOpt("help", "debug", "after=i", "before=i", "unlimited", "blank_ignore", "predecessor", "fmt=s@", "linenumbers|numbers", "nocontext|ncontext", "noheaders|nheaders", "noprologue|nprologue"); &Usage(1) if ($opt_help); ## Force the -pred option if only one XNAME argument is supplied if ( @ARGV == 1 ) { $opt_predecessor = 1; } ## Check for errors if ( @ARGV == 0) { &ErrorMsg("Too few arguments given."); $rc = 0; } &Usage(2) if (! $rc); ## Set up and verify any specified formats &SetupFormats(@opt_fmt) || exit(2); ## Set up default number of context lines $DEFAULT_CONTEXT = ($FMT{'patch'} ? 3 : 5); $opt_before = $DEFAULT_CONTEXT unless ($opt_before); $opt_after = $DEFAULT_CONTEXT unless ($opt_after); ## Verify the versions exist @FILELIST = (); foreach (@ARGV) { if (/^(.+)${CLEARCASE_XN_SFX}(.+)$/o) { ($ELEMENT, $SELECTOR) = ($1, $2); ##$VERPATH = &clearlib'CanonXPName($ELEMENT, $SELECTOR); $VERPATH = $SELECTOR; &FatalMsg("Can't find $_") unless ($VERPATH); $XPNAME = $ELEMENT . $CLEARCASE_XN_SFX . $VERPATH; } else { $XPNAME = $_; if (-e $_) { $ELEMENT = $_; } else { ##$VERPATH = &clearlib'CanonXPName($ELEMENT, $_) if ($ELEMENT); $VERPATH = $_ if ($ELEMENT); $XPNAME = $ELEMENT . $CLEARCASE_XN_SFX . $VERPATH; } &FatalMsg("Can't find $_") unless ($XPNAME); } if ($XPNAME !~ /^.+${CLEARCASE_XN_SFX}.+$/o) { &FatalMsg("Can't find $_") unless (-e $XPNAME); } push(@FILELIST, $XPNAME); &FatalMsg("$XPNAME is a directory") if (-d $XPNAME); &FatalMsg("$XPNAME is a binary file") if (-B $XPNAME); } $changes = &ContextDiff(@FILELIST); exit($changes);