Files
2025-09-29 00:52:08 +02:00

304 lines
11 KiB
Ruby
Executable File

#
# File:: util/p4_diff.rb
# Description:: Perforce diff script for diffing files between two Perforce
# servers.
#
# Author:: David Muir <david.muir@rockstarnorth.com>
# 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