#!/usr/bin/ruby #******************************************************************************* #* A Ruby script to detect changes to different types of spec ( client/label/ #* branch ) and to record the changes by checking the specs in as text files #* into a dedicated area of the depot. Designed to be periodically executed #* via either cron or the Windows scheduler depending on platform. #* #* Note: This script REQUIRES a working build of P4Ruby which you can #* get from the Perforce public depot in #* #* //guest/tony_smith/perforce/API/Ruby/... #* #* You'll also need the Perforce API for your platform to build P4Ruby. #* #******************************************************************************* #******************************************************************************* #* CONFIGURATION SECTION #******************************************************************************* # Depot path under which specs should be versioned. This path should not # already exist. SPEC_PATH = "//depot/specs" # Name of the counter to use to keep track of update runs. One counter is # used for *all* types of spec. SPEC_COUNTER = "specsaver" # Name of the field in your jobspec which is used as the update date JOB_DATE_FIELD = "Date" # Perforce environment setup P4USER = "perforce" P4PORT = "localhost:1666" P4PASSWD = "" P4CLIENT = "specsaver" # Set the client root to a path that the script can use as its workspace CLIENTROOT = "setme" #******************************************************************************* #* END OF CONFIGURATION SECTION #******************************************************************************* require "P4" # Virtual base class for handling all types of spec. For each spec you want # to manage, derive a class which must implement at least the following # methods: # # list_specs - list specs ("p4 clients"/"p4 labels" etc.) # spec_file - Locate spec file in workspace # class SpecMgr def initialize( root, p4tagged, p4untagged ) @root = root @p4t = p4tagged @p4u = p4untagged end def changed?( spec, stamp ) spec[ "Update" ].to_i > stamp end def add_edit_file( path ) fs = @p4t.run_fstat( path ).shift if ( fs ) @p4t.run_edit( path ) else @p4t.run_add( "-t", "text", path ) end end # # Compute the name of a spec from its type. Necessary because the # field for jobs is "Job" whilst for clients it's "client" ... # def type2name( type, spec ) return spec[ type ] if spec.has_key?( type ) return spec[ type.capitalize ] if spec.has_key?( type.capitalize ) raise( RuntimeError, "Can't determine object name from type" ) end # # Save the named spec into its text file version. As we don't want # the spec in parsed form for this we instantiate another P4 instance # briefly here to run the "p4 xxxx -o" in non-tagged mode and the # output of that command is written to the workspace file. # def save_spec( type, spec ) path = spec_file( spec ) add_edit_file( path ) name = type2name( type, spec ) form = eval( %Q{@p4u.fetch_#{type}( "#{name}" )} ) write_ws_file( path, form ) submit( "Updated copy of #{type} #{name}" ) end def write_ws_file( path, form ) File.open( path, "w+" ) do |file| file.write( form ) end end def submit( desc ) @p4t.run_revert( "-a" ) change = @p4t.fetch_change if ( change.has_key?( "Files" ) ) puts( desc ) change[ "Description" ] = desc @p4t.submit_spec( change ) end end # Update the specs of the specified type def update_specs( type, since ) list_specs.each do |spec| save_spec( type, spec ) if ( changed?( spec, since ) ) end end end class GenSpecMgr < SpecMgr def initialize( type, root, p4t, p4u ) @type = type super( root, p4t, p4u ) end def list_specs eval( %Q{@p4t.run_#{@type}s} ) end def spec_file( spec ) @root + "/#{@type}s/" + type2name( @type, spec ) end def update_specs( since ) super( @type, since ) end end # Subclass for clients class ClientMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "client", root, p4t, p4u ) end end # Subclass for labels class LabelMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "label", root, p4t, p4u ) end end # Subclass for users class UserMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "user", root, p4t, p4u ) end end # Subclass for groups. Groups don't have an update date so we have # to depend on revert -a doing the honours. "p4 groups" also doesn't # support tagged output at the moment (2002.1) so what are hashes in # the other classes are just group names in this one. class GroupMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "group", root, p4t, p4u ) end def changed?( group, since ) true end def spec_file( group ) @root + "/#{@type}s/" + group end def type2name( type, group ) group end end # Subclass for depots. "p4 depots" doesn't support tagged mode so we # do this the simple way. class DepotMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "depot", root, p4t, p4u ) end def changed?( depot, since ) true end def spec_file( depot ) @root + "/#{@type}s/" + depot end def type2name( type, depot ) depot end def list_specs @p4t.run_depots.collect do |rec| rec.split[ 1 ] end end end # Subclass for branches. As the plural of branch is branchES, this needs # a separate subclass of its own. class BranchMgr < SpecMgr def list_specs @p4t.run_branches end def spec_file( spec ) @root + "/branches/" + spec[ "branch" ] end def update_specs( since ) super( "branch", since ) end end # Subclass for jobs class JobMgr < SpecMgr # Get a list of labels from the server. def list_specs( stamp ) @p4t.run_jobs( "-e", "#{JOB_DATE_FIELD} > #{stamp}" ) end # Locate the workspace file for a given spec def spec_file( spec ) @root + "/jobs/" + spec[ "Job" ] end def update_specs( since ) list_specs( since ).each do |spec| save_spec( "job", spec ) end end end # Subclass for one off's like jobspec and protections. These are a little # different as "there can be only one!" We also can't use a timestamp to # see if they've changed so we rely on "p4 revert -a" class ConfigMgr < SpecMgr def update( name, label ) path = @root + "/" + name add_edit_file( path ) form = eval %Q{@p4u.fetch_#{name}()} write_ws_file( path, form ) submit( "Updated copy of #{label}" ) end end # # Main top level class for co-ordinating the run. Here we can trap # Perforce exceptions and look after the overall environment. # class SpecSaver SPECS = [ "branches", "clients", "depots", "groups", "jobs", "labels", "users" ] def initialize # Perforce client for handling tagged output @p4t = P4.new @p4t.parse_forms @p4t.exception_level = 1 @p4t.debug = 0 @p4t.port = P4PORT @p4t.user = P4USER @p4t.password = P4PASSWD @p4t.client = P4CLIENT @p4t.connect # Perforce client for non-tagged output @p4u = P4.new @p4u.exception_level = 1 @p4u.debug = 0 @p4u.port = P4PORT @p4u.user = P4USER @p4u.password = P4PASSWD @p4u.client = P4CLIENT @p4u.connect # Set the client root @root = CLIENTROOT init_client() end # Create if necessary def init_client() begin @p4t.run_info.each do |line| create_client() if ( line =~ /^Client unknown./ ) end init_workspace() @p4t.run_sync rescue P4Exception @p4t.errors.each { |e| puts( e ) } raise end end def init_workspace() Dir.mkdir( @root ) unless ( File.exists?( @root ) ) SPECS.each do |type| dir = @root + "/" + type Dir.mkdir( dir ) unless ( File.exists?( dir ) ) end end # Create the client workspace. def create_client() begin spec = @p4t.fetch_client spec[ "Description" ] = "Client for spec versioning script" spec[ "Root" ] = @root # Don't use String#gsub! it seems to cause problems opt = spec[ "Options" ].gsub( "normdir", "rmdir" ) spec[ "Options" ] = opt spec[ "View" ] = Array.new spec[ "View" ].push( SPEC_PATH + "/... //" + @p4t.client? + "/..." ) @p4t.save_client( spec ) rescue P4Exception @p4t.errors.each { |e| puts( e ) } end end def counter? c = @p4t.run_counter( SPEC_COUNTER ).shift.to_i return c end def counter=( val ) @p4t.run_counter( SPEC_COUNTER, val ) true end def update # Timestamp for this run now = Time.now.to_i # time of last update since = counter? begin ClientMgr.new( @root, @p4t, @p4u ).update_specs( since ) LabelMgr.new( @root, @p4t, @p4u ).update_specs( since ) BranchMgr.new( @root, @p4t, @p4u ).update_specs( since ) JobMgr.new( @root, @p4t, @p4u ).update_specs( since ) UserMgr.new( @root, @p4t, @p4u ).update_specs( since ) GroupMgr.new( @root, @p4t, @p4u ).update_specs( since ) DepotMgr.new( @root, @p4t, @p4u ).update_specs( since ) c = ConfigMgr.new(@root, @p4t, @p4u ) c.update( "protect", "protections" ) c.update( "jobspec", "jobspec" ) c.update( "typemap", "typemap" ) # Save the counter. Any of the Mgr vars will do. self.counter = now rescue P4Exception @p4t.errors.each{ |e| puts( e ) } @p4u.errors.each{ |e| puts( e ) } end end end #******************************************************************************* #* START OF MAIN SCRIPT #******************************************************************************* # Sanity check! if ( CLIENT_ROOT == "setme" ) raise( RuntimeError, "Error: Client root not set. " + "Edit script and set client root before retrying" ) end w = SpecSaver.new w.update
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#11 | 3094 | Tony Smith |
Tweaks to specsaver to (a) tidy up and (b) make it rdoc ready. Docs to follow |
||
#10 | 2740 | Tony Smith |
Followup to change #2678. Make the change description itself simple, but include the list of updated objects in the output of the script - mostly because that's the way I use it. |
||
#9 | 2678 | Tony Smith | Tweak specsaver.rb for clearer descriptions | ||
#8 | 1937 | Tony Smith |
Adapt specsaver.rb to batch up submissions by type. So all the changed userspecs are submitted together, same with groups, branches labels etc. |
||
#7 | 1936 | Tony Smith | Add --debug flag to specsaver so you can see what's going on. | ||
#6 | 1935 | Tony Smith |
Tweak label versioning. This is nicer. |
||
#5 | 1910 | Tony Smith |
Update specsaver.rb to take command line arguments specifying what to version. Syntax is now: specsaver.rb [ options ] where options are: --all - archive all types of object --branches - archive branch specs --clients - archive client specs --config - archive typemap, protections and jobspec --depots - archive depot definitions --groups - archive group specs --jobs - archive jobs (but not fixes) --labels - archive label views and files --users - archive user specs (not passwords) |
||
#4 | 1896 | Tony Smith | Some cosmetic tidying up following previous change. | ||
#3 | 1895 | Tony Smith |
Fix typo, and add support for proper versioning of labels. Now not only does it save the label spec, but it also runs a "p4 files //...@label" for each label and saves the file list alongside the spec. |
||
#2 | 1872 | Tony Smith |
Updated version of specsaver.rb. This version adds support for versioning of user and group specs (no, passwords are not included) and contains some general tidying up. Currently only works out of the box on Unix. |
||
#1 | 1871 | Tony Smith |
First release of specsaver.rb a script to version all "specs" in your depot. Currently this means, clients/labels/branches and protections, typemaps and jobspecs. Groups and users to follow. It does not currently handle deletion of these objects. |