# # File:: util/p4_diff.rb # Description:: Perforce diff script for diffing files between two Perforce # servers. # # Author:: David Muir # Date:: 12 November 2009 # #---------------------------------------------------------------------------- # Uses #---------------------------------------------------------------------------- require 'pipeline/config/projects' require 'pipeline/gui/exception_dialog' require 'pipeline/os/getopt' require 'pipeline/os/start' require 'pipeline/scm/perforce' require 'pipeline/scm/perforce_helper' include Pipeline #---------------------------------------------------------------------------- # Constants #---------------------------------------------------------------------------- OPTIONS = [ [ '--source_port', '-p', OS::Getopt::REQUIRED, 'source Perforce server (and port).' ], [ '--source_client', '-c', OS::Getopt::REQUIRED, 'source client/workspace name.' ], [ '--source_user', '-u', OS::Getopt::REQUIRED, 'source username.' ], [ '--source_path', '-s', OS::Getopt::REQUIRED, 'source path mapping.' ], [ '--ignore', '-i', OS::Getopt::REQUIRED, 'source paths to ignore.' ], [ '--target_port', '-p', OS::Getopt::REQUIRED, 'target Perforce server (and port).' ], [ '--target_client', '-c', OS::Getopt::REQUIRED, 'target client/workspace name.' ], [ '--target_user', '-v', OS::Getopt::REQUIRED, 'target username.' ], [ '--target_path', '-t', OS::Getopt::REQUIRED, 'target path mapping.' ], [ '--use_gui', '-g', OS::Getopt::BOOLEAN, 'use p4merge GUI utility to show output.' ], [ '--brief', '-b', OS::Getopt::BOOLEAN, 'Output only whether files differ.' ], [ '--output_file', '-o', OS::Getopt::REQUIRED, 'output report filename.' ], [ '--help', '-h', OS::Getopt::BOOLEAN, 'display usage information.' ], ] #---------------------------------------------------------------------------- # Functions #---------------------------------------------------------------------------- # Convert source path String to destination path String. def path_src_to_dst( source, target, filename ) src = source[:path].sub( '...', '' ) dst = target[:path].sub( '...', '' ) filename.sub( src, dst ) end # Determine whether we should ignore this source file? def path_src_to_ignore?( source, filename ) norm_filename = OS::Path::normalise( filename ) source[:ignore].each do |path| src = path.sub( '...', '' ) norm_src = OS::Path::normalise( src ) return true if ( norm_filename.starts_with( norm_src ) ) end false end # Diff an individual file using either GNU diff or P4Merge (use_gui). # GNU Diff will output some report information based on its return # status. def diff_file( source, target, log, src_file, use_gui, brief ) differ = false begin # ignore files that match the ignore list if path_src_to_ignore?(source, src_file) then log.info("###### Ignored file #{src_file} ########") return false end source[:p4].connect() unless ( source[:p4].connected?() ) target[:p4].connect() unless ( target[:p4].connected?() ) if ( not source[:p4].connected?() ) log.error("source P4 #{source[:p4].port.to_s} is not connected - cannot proceed with diff. File #{src_file.to_s} skipped!") return true end if ( not target[:p4].connected?() ) log.error("target P4 #{target[:p4].port.to_s} is not connected - cannot proceed with diff.File #{src_file.to_s} skipped!") return true end dst_file = path_src_to_dst( source, target, src_file ) fstat = source[:p4].run_fstat( src_file ).shift # src_file_local = OS::Path::platform_native( source[:p4].depot2local( fstat['depotFile'] ) ) src_file_local = OS::Path::platform_native( fstat['clientFile'] ) dst_as_depot_file = target[:p4].depot2local( dst_file ) dst_file_local = OS::Path::platform_native( dst_as_depot_file ) if ( dst_file.nil? or dst_file_local.nil? ) then log.error( "Unable to determine destination file from source: #{src_file}. File skipped!" ) return true end # check if files exists - do not diff files that don't exist. src_file_exists = File.exist?(src_file_local) dst_file_exists = File.exist?(dst_file_local) if (src_file_exists and dst_file_exists ) then log.info( "\t#{src_file} #{dst_file} : Both files exist." ) elsif (src_file_exists and not dst_file_exists ) log.error( "\t#{dst_file} : File does not exist.") return false elsif (dst_file_exists and not src_file_exists ) log.error( "\t#{src_file} : File does not exist.") return false else log.info( "\tBoth files do not exist.") log.info( "\t#{src_file} : File does not exist.") log.info( "\t#{dst_file} : File does not exist.") return false end log.info( "Diff files: " ) log.info( "\t#{src_file} and" ) log.info( "\t#{dst_file}" ) c = Pipeline::Config::instance() commandline_merge = "p4merge.exe #{src_file_local} #{dst_file_local}" if ( use_gui ) then log.info( "Executing: #{commandline_merge}" ) system( commandline_merge ) else #files = source[:p4].run_files( src_file ).shift binary = true if (fstat["headType"].include?("binary")) options = "-u --ignore-space-change" options += " --binary" if binary options += " --text" unless binary # DW - 12-04-2010 - possibly faster? options += " --brief" if binary or brief commandline = "#{OS::Path::platform_native( OS::Path::combine( c.toolsbin, 'diff.exe' ))} #{options} \"#{src_file_local}\" \"#{dst_file_local}\"" log.info( "Executing: #{commandline}" ) status, sout, serr = OS::start( commandline ) case status.exitstatus when 0 log.info( "\tDo not differ." ) when 1, 2 differ = true filetype = binary ? "Binary" : "Text" out_string = "\t#{src_file}\t#{dst_file} : #{filetype} files differ" out_string += " : [ #{commandline_merge} ]" unless (binary) log.error( out_string ) sout.each do |line| log.info( "\t\t#{line}" ) end serr.each do |line| log.info( "\t\t#{line}" ) end end end rescue Exception => ex log.exception( ex, "Unhandled exception integrating file #{src_file} #{ex.message}" ) end return differ end #---------------------------------------------------------------------------- # Entry #---------------------------------------------------------------------------- if ( __FILE__ == $0 ) then difference_found = false begin g_AppName = OS::Path::get_basename( __FILE__ ) g_Config = Pipeline::Config.instance( ) g_Source = {} # Source Perforce server settings g_Target = {} # Target Perforce server settings #-------------------------------------------------------------------- # Parse Command Line #-------------------------------------------------------------------- g_Options, g_Trailing = OS::Getopt.getopts( OPTIONS ) g_Debug = g_Options['debug'].nil? ? false : true g_UseGUI = g_Options['use_gui'].nil? ? false : true g_Brief = g_Options['brief'].nil? ? false : true # Force log output g_Config::log_trace = g_Debug g_Config::logtostdout = true g_Config::log_level = 2 g_Config::log_level = 1 if ( g_Debug ) g_Log = Log::new( g_AppName ) if ( g_Options['help'] ) then puts OS::Getopt.usage( OPTIONS ) exit( 1 ) end #-------------------------------------------------------------------- # Parse Command Line : Source Arguments and Validate #-------------------------------------------------------------------- g_Source[:port] = g_Options['source_port'] g_Source[:client] = g_Options['source_client'] g_Source[:user] = g_Options['source_user'] g_Source[:path] = g_Options['source_path'] g_Source[:ignore] = g_Options['ignore'].split( ',' ) unless ( g_Options['ignore'].nil? ) g_Source[:ignore] = [] if ( g_Options['ignore'].nil? ) # Validate g_Source.each_pair do |k, v| next unless ( v.nil? ) g_Log.error( "source settings incomplete (#{k})." ) puts OS::Getopt::usage( OPTIONS, TRAILING ) exit( 2 ) end #-------------------------------------------------------------------- # Parse Command Line : Target Arguments and Validate #-------------------------------------------------------------------- g_Target[:port] = g_Options['target_port'] g_Target[:client] = g_Options['target_client'] g_Target[:user] = g_Options['target_user'] g_Target[:path] = g_Options['target_path'] g_Target[:autosubmit] = g_Options['autosubmit'] unless ( g_Options['autosubmit'].nil? ) g_Target[:autosubmit] = false if ( g_Options['autosubmit'].nil? ) # Validate g_Target.each_pair do |k, v| next unless ( v.nil? ) g_Log.error( "target settings incomplete (#{k})." ) puts OS::Getopt::usage( OPTIONS, TRAILING ) exit( 3 ) end #-------------------------------------------------------------------- # Additional Validation # DW - 8/4/10 - Currently disabled as agreed with DM, we do wish to be able to diff on the same server # this was in only since p4_integrate.rb wanted to prevent us integrating on same port. #-------------------------------------------------------------------- #if ( 0 == g_Source[:port].casecmp( g_Target[:port] ) ) then # g_Log.error( "source and target ports are the same. Crazy fool use 'p4 integ'." ) # puts OS::Getopt::usage( OPTIONS, TRAILING ) # exit( 4 ) #end #-------------------------------------------------------------------- # Diff #-------------------------------------------------------------------- # Create Perforce connection objects. g_Source[:p4] = SCM::Perforce.new( ) g_Source[:p4].port = g_Source[:port] g_Source[:p4].client = g_Source[:client] g_Source[:p4].user = g_Source[:user] g_Source[:p4].connect( ) g_Target[:p4] = SCM::Perforce.new( ) g_Target[:p4].port = g_Target[:port] g_Target[:p4].client = g_Target[:client] g_Target[:p4].user = g_Target[:user] g_Target[:p4].connect( ) # Loop through files and changelists specified as trailing arguments. g_Trailing.each do |filename| # Filename or filespec. g_Log.info( "Diff Filespec: #{filename}" ) # Run fstat so we can support filespecs properly (e.g. //depot/..., //depot/*) # Then use its output as filename to actually integrate. fstat = g_Source[:p4].run_fstat( filename ) fstat.each do |fs| files = fs['depotFile'] files.each do |filename| if diff_file( g_Source, g_Target, g_Log, filename, g_UseGUI, g_Brief ) then difference_found = true end end end end rescue SystemExit => ex exit( ex.status ) rescue Exception => ex g_Log.exception( ex, 'Unhandled exception' ) puts "Unhandled exception: #{ex.message}" puts ex.backtrace.join("\n") puts "Press any key to continue..." #$stdin.gets() end exit -1 if difference_found exit 0 unless difference_found end # util/p4_integrate.rb