#!/usr/local/bin/ruby #******************************************************************************* # Copyright (c) 2007, Perforce Software, Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #******************************************************************************* # # convarchives.rb - A script to incrementally copy a set of Perforce archives # from one location to another and convert the line endings # from dos to unix as it goes. Also renames the files and # directories to lower-case: suitable for use with # 'p4d -C1' type migrations. # # This script makes extensive use of subprocesses to speed # up the translation so it will keep your machine very # busy indeed. # # Requires: d2u (also from Perforce public depot) # require "getoptlong" #------------------------------------------------------------------------------- # Start of Configuration section #------------------------------------------------------------------------------- # # Where to find line ending converter. Use d2u from the same location as # this script in the Perforce public depot. # D2U = '/usr/local/bin/d2u' #------------------------------------------------------------------------------- # End of Configuration section #------------------------------------------------------------------------------- class FileData def initialize( mtime, size ) @mtime = mtime @size = size end attr_accessor :mtime, :size end # # Class to traverse an input tree and copy/convert the files it finds as # we go # class Translator def initialize( inroot, outroot, verbose = false ) @inroot = inroot @outroot = outroot @verbose = verbose @childcount = 0 if( ! File.directory?( inroot ) ) raise( "Input path must be a directory" ) end if( !File.directory?( outroot ) ) raise( "Output path must be a directory" ) end end # # Traverse the input tree. # def update() transtree( @inroot, @outroot ) # Wait for all child processes to terminate begin while true Process.wait() end rescue SystemCallError end end def outpath( inpath ) inpath.sub( @inroot, @outroot ) end def mkdir( path ) unless File.directory?( path ) Dir.mkdir( path ) end end def transtree( in_r, out_r ) mkdir( out_r ) Dir.foreach( in_r ) do |ent| next if( ent == "." || ent == ".." ) in_p = in_r + '/' + ent out_p = outpath( out_r + '/' + ent.downcase ) if( File.directory?( in_p ) ) mkdir( out_p ) create_child( in_p, out_p ) else transfile( in_p, out_p ) end end end def create_child( in_r, out_r ) # Wait until we have at most one other child running. We don't # want to flood the system with too many processes. while( @childcount > 1 ) Process.wait() @childcount -= 1 end begin pid = Process.fork() rescue # Too many processes? Sleep a little, then retry sleep( rand( 5 ) ) retry end if( pid ) @childcount += 1 else # Child process if @verbose exec( $0, "-v", "-c", in_r, out_r ) else exec( $0, "-c", in_r, out_r ) end end end def trans_cmd( in_f, out_f ) cmd = %Q{#{D2U} < "%s" > "%s" 2>/dev/null} return sprintf( cmd, in_f, out_f ) end def cp_cmd( in_f, out_f ) cmd = %Q{cp -f "%s" "%s"} return sprintf( cmd, in_f, out_f ) end def cleanpath( p ) p.gsub( /\$/, '\$' ) end def transfile( in_f, out_f ) ist = File.stat( in_f ) if( File.exists?( out_f ) ) ost = File.stat( out_f ) return if( ist.mtime == ost.mtime ) end if( in_f[ -2 .. -1 ] == ",v" ) puts( "Translating: #{in_f}" ) if @verbose cmd = trans_cmd( cleanpath( in_f ), cleanpath( out_f ) ) # puts( "Cmd: #{cmd}" ) if @verbose if( ! system( cmd ) ) raise( "Failed to run d2u #{in_f}" ) end else puts( "Copying: #{in_f}" ) if @verbose cmd = cp_cmd( cleanpath( in_f ), cleanpath( out_f ) ) if( ! system( cmd ) ) raise( "Failed to copy #{in_f}" ) end end # # Update timestamp on file # File.utime( ist.atime, ist.mtime, out_f ) end end def usage() puts( "Usage: convarchives.rb " ) exit( 0 ) end def main verbose = false ischild = false opts = GetoptLong.new( [ "-c", GetoptLong::NO_ARGUMENT ], [ "-v", GetoptLong::NO_ARGUMENT ] ) opts.each do |opt,arg| if( opt == "-c" ) ischild = true elsif( opt == "-v" ) verbose = true end end if( ARGV.length() != 2 ) usage() end stime = Time.now if( !ischild ) puts( "Converting from: " + ARGV[0] + " to: " + ARGV[1] ) puts( "Start Time: " + stime.to_s ) end trans = Translator.new( ARGV.shift, ARGV.shift, verbose ) trans.update() if( !ischild ) etime = Time.now puts( "End Time: " + etime.to_s ) puts( "Duration: %d seconds" % ( etime.to_i - stime.to_i ) ) end end if $0 == __FILE__ main end