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 debugmy $opt_verbose = $ENV{'GBE_VERBOSE'}; # Allow global verbosemy $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 interfacemy $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 directoryif (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 foundif ( -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 = $1if ( $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 NAMEjats_vcsdiff - Difference two views=head1 SYNOPSISjats 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 firstview 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 programwill 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 otherwisethe 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 thepatch 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=backThis option will force the use of a 'diff' utility on both Windows andUnix.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 usersresponsibility 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. Thistag is mandatory.The old and new tags may be provided on the command line, or via namedoptions, but not both.The tag may be of the form dir=path to force the utility to use a localview or path.Additional tag values are available when generating differences against packagesthat are released in Release Manager. In order to do this specific -rtagid and-package to specify the Release Manager package version. Oncethese are specified the following tags can be used:=over 4=item * releasedUse the current released package version in Release Manager.=item * headUse the Subversion HEAD of the Release Manager release path.=item * trunkUse the HEAD of the Subversion trunk.=item * branch=xxxUse 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. Thistag 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 URLused 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 bein 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 namein the release given by -rtagid.=back=head1 DESCRIPTIONThis 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 twoVCS 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=backThe program will:=over 8=item *Create two 'extract only' views based on the VCS Tags provided. The resultantviews 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.=backIf one of the Vcs Tags is of the form:=over 8=item * current=item * current=path=item * dir=path=backThen the tag will be treated as a directory and will be used for one sideof the comparison.If a Vcs Tag is of the form:=over 8=item * released=item * head=item * trunk=item * branch=xxx=backThen the source code is checked out of source control based on a Release Managerrtagid (-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 EXAMPLEThe 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 -checkThe following command will compare a Subversion View with a local directoryjats vcsdiff SVN::AUPERASVN01/COTS/crc/tags/crc_26.4.0000.cr dir=crcThe following command will compare the release version of the ct-spa package from Pulse 2.7.0 with the current head of that packagejats vcsdiff -rtagid=38970 -package=ct-spa released headThe following command will create a 'patch' file between the specified version of the crc package and the version in thedirectory named 'crc'.ats vcsdiff SVN::AUPERASVN01/COTS/crc/tags/crc_26.4.0000.cr dir=crc -patch=cut