304 lines
11 KiB
Ruby
Executable File
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
|