#!/usr/bin/ruby #-- #------------------------------------------------------------------------------- #++ #== Introduction # # This script keeps a slave branch synchronised with a master branch based # on the contents of a branch view. This could mean a single file, or an # entire tree - it's up to you. There are two ways to limit the scope of # the script: # # 1. Use a limited mapping in the trigger specification # 2. Use a limited mapping in the branch specification # # As you can see, the methods are similar. # #== Requirements # # Ruby # http://www.ruby-lang.org # # P4Ruby # http://public.perforce.com/guest/tony_smith/perforce/API/Ruby/index.html # # Rubymail # http://raa.ruby-lang.org/project/rubymail/ # #== Trigger Specification # # An suitable trigger specification might look like this: # # autointeg commit //depot/master/... "t/autointeg.rb -b ms -c %changelist%" # # This would cause all changes to files under //depot/master/... that are # mapped by the branch specification 'ms' (master-slave) to be propagated # immediately to the slave copy. # #-- #------------------------------------------------------------------------------- #++ require "P4" require "getoptlong" require "net/smtp" require "rmail/message" #-- #------------------------------------------------------------------------------- # CONFIGURATION SECTION #------------------------------------------------------------------------------- #++ # Email address of the Perforce administrator - notifications will be # sent to this user if EMAIL_REPORTS is true ADMIN_EMAIL = "perforce@localhost" # If true, reports of the integrations performed will be emailed to the # address above. Default: false. EMAIL_REPORTS = false # The name of your SMTP server. The script uses SMTP to send notifications # by email if EMAIL_REPORTS is true. SMTP_SERVER = "localhost" # The email address messages from this script should appear to come from FROM_ADDRESS = "perforce@localhost" # The subject line for the messages MSG_SUBJECT = "Automatic integration report" # Causes the text of the email that would be sent to be dumped to stderr - # which in the case of a trigger means it goes to the client. DEBUG = false # # Perforce environment. Unless these values are explicitly specified # here, all the Perforce settings will be taken from the calling environment. # This means you can use it with P4CONFIG files, and with 'p4 login' # tickets if you want to. If you use tickets, make sure the script user is # in a group with a really, really long ticket timeout. P4USER = nil P4PORT = nil P4CLIENT = nil P4PASSWD = nil #-- #------------------------------------------------------------------------------- # END OF CONFIGURATION SECTION #------------------------------------------------------------------------------- #++ # # This simple class encapsulates the functionality of this script. Essentially, # we integrate using a branchspec and auto resolve with 'Accept Theirs'. This # means that there's no support for maintaining variant branches here, only # master/slave branches. Useful for so-called 'shared files' (VSS-style). # class IntegMgr def initialize( p4, branchspec ) @p4 = p4 @branch = branchspec end attr_reader :p4, :branch # # Integrate the specified change through the branch view and resolve # them. # def integrate( change ) p4.run_sync c = create_change( change ) p4.run_integrate( "-c", c, "-b", branch, "-s", "//...@#{change},@#{change}" ) p4.run_resolve( "-at" ) # Build a list of the files to be submitted. filelist = p4.run_opened( "-c", c ).collect do |h| sprintf( "%s#%s (%s)", h[ "depotFile" ], h[ "rev" ], h[ "action" ] ) end # If no files were opened for integrate, there's nothing to do, so # we just delete our pending changelist and get out of here if filelist.length == 0 p4.delete_change( c ) return end # Now submit it. p4.run_submit( "-c", c ) admin_report do "Change #{change} automatically integrated using branchspec " + @branch + "\n\n" + "Affected Files:\n\n\t" + filelist.join( "\n\t" ) + "\n" end end # Create a new empty changelist for this transaction def create_change( changeno ) spec = p4.fetch_change spec[ "Files" ] = Array.new spec[ "Jobs" ] = Array.new spec[ "Description" ] = "Automatically propagate change #{changeno} " + "to slave branch." change = p4.save_change( spec ).shift if( change =~ /^Change (\d+)/ ) return $1 end raise( P4Exception, "Unable to create an empty pending changelist" ) end end # # Blurt out our syntax # def croaksyntax() puts < -c EOS exit 0 end # # Send a report by email to the admin # def admin_report( &block ) msg = RMail::Message.new msg.header[ 'To' ] = ADMIN_EMAIL msg.header[ 'From' ] = FROM_ADDRESS msg.header[ 'Subject' ] = MSG_SUBJECT msg.body = block.call() if DEBUG $stderr.puts( msg.to_s ) end if EMAIL_REPORTS Net::SMTP.start( SMTP_SERVER ) do |smtp| smtp.sendmail( msg, FROM_ADDRESS, ADMIN_EMAIL ) end end end #-- #------------------------------------------------------------------------------- # Start of main script #------------------------------------------------------------------------------- #++ opts = GetoptLong.new( [ "-b", GetoptLong::REQUIRED_ARGUMENT ], [ "-c", GetoptLong::REQUIRED_ARGUMENT ] ) branch = nil change = nil # Parse command line opts.each do |opt,arg| case opt when "-b" then branch = arg when "-c" then change = arg end end croaksyntax unless ( branch && change ) # # Command line is valid. Initialize our Perforce client and get to work. # p4 = P4.new p4.user = P4USER if P4USER p4.client = P4CLIENT if P4CLIENT p4.port = P4PORT if P4PORT p4.password = P4PASSWD if P4PASSWD p4.parse_forms p4.exception_level = 1 p4.debug = 0 begin p4.connect mgr = IntegMgr.new( p4, branch ) mgr.integrate( change ) rescue P4Exception admin_report do "Automatic integration failed with errors. Detail follows\n\n" + "Error: #{$!.message}\n\n" + "Perforce Errors:\n\t" + p4.errors.join( "\n\t" ) + "Traceback:\n\t" + $!.backtrace.join( "\n\t" ) end rescue admin_report do "Automatic integration failed with errors. Detail follows\n\n" + "Error: #{$!.message}\n\n" + "Traceback:\n\t" + $!.backtrace.join( "\n\t" ) end end