#!/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 = "/home/tony/p4-specmgr" #******************************************************************************* #* END OF CONFIGURATION SECTION #******************************************************************************* require "P4" require "getoptlong" # 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 ) 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( type, since ) list_specs.each do |spec| save_spec( type, spec ) if ( changed?( spec, since ) ) end submit( description() ) 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( since ) super( @type, since ) end def description "Archive updated #{@type}s" 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 # For labels we update the files list as well as the spec def update( since ) list_specs.each do |l| if ( changed?( l, since ) ) save_spec( "label", l ) save_files( l[ "label" ] ) end end submit( description() ) end def save_files( label ) filename = @root + "/labels/" + label + ".files" files = @p4t.run_files( "//...@#{label}" ).collect do |l| l[ "depotFile" ] + "#" + l[ "rev" ] end add_edit_file( filename ) write_ws_file( filename, files.join( "\n" ) ) 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( since ) super( "branch", since ) end def description "Archive updated branches" 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( since ) list_specs( since ).each do |spec| save_spec( "job", spec ) end submit( description() ) end def description "Archive updated jobs" 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( "Archive updated #{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 @flags = Hash.new # Perforce client for handling tagged output @p4t = P4.new @p4t.parse_forms @p4t.exception_level = 1 @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.port = P4PORT @p4u.user = P4USER @p4u.password = P4PASSWD @p4u.client = P4CLIENT @p4u.connect # Set the client root @root = CLIENTROOT init_client() end def debug( level ) $stderr.puts( "Setting debug level to #{level}" ) @p4u.disconnect @p4t.disconnect @p4u.debug = level @p4t.debug = level @p4u.connect @p4t.connect 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 # Resolve missing methods by looking in the flags hash. def method_missing( m, *args ) method = m.to_s if ( method !~ /\?$/ ) # Assignment return @flags[ method ] = true else method.gsub!( "\\?$", "" ) end if ( @flags.has_key?( method ) ) @flags[ method ] elsif ( @flags.has_key?( "all" ) ) @flags[ "all" ] else false end end def have_flags? return ! @flags.empty? end def update # Timestamp for this run now = Time.now.to_i # time of last update since = counter? begin branches = BranchMgr.new( @root, @p4t, @p4u ) clients = ClientMgr.new( @root, @p4t, @p4u ) depots = DepotMgr.new( @root, @p4t, @p4u ) groups = GroupMgr.new( @root, @p4t, @p4u ) jobs = JobMgr.new( @root, @p4t, @p4u ) labels = LabelMgr.new( @root, @p4t, @p4u ) users = UserMgr.new( @root, @p4t, @p4u ) config = ConfigMgr.new( @root, @p4t, @p4u ) branches.update( since ) if ( self.branches? ) clients.update( since ) if ( self.clients? ) depots.update( since ) if ( self.depots? ) groups.update( since ) if ( self.groups? ) jobs.update( since ) if ( self.jobs? ) labels.update( since ) if ( self.labels? ) users.update( since ) if ( self.users? ) if ( self.config? ) config.update( "protect", "protections" ) config.update( "jobspec", "jobspec" ) config.update( "typemap", "typemap" ) end # Save the counter. self.counter = now rescue P4Exception @p4t.errors.each{ |e| puts( e ) } @p4u.errors.each{ |e| puts( e ) } end end end def croak_syntax puts( %Q{ Usage: 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) } ) exit( 0 ) end #******************************************************************************* #* START OF MAIN SCRIPT #******************************************************************************* # Parse command line options opts = GetoptLong.new ( [ "--all", GetoptLong::NO_ARGUMENT ], [ "--branches", GetoptLong::NO_ARGUMENT ], [ "--clients", GetoptLong::NO_ARGUMENT ], [ "--config", GetoptLong::NO_ARGUMENT ], [ "--debug", GetoptLong::OPTIONAL_ARGUMENT ], [ "--depots", GetoptLong::NO_ARGUMENT], [ "--groups", GetoptLong::NO_ARGUMENT ], [ "--jobs", GetoptLong::NO_ARGUMENT ], [ "--labels", GetoptLong::NO_ARGUMENT ], [ "--users", GetoptLong::NO_ARGUMENT ] ) s = SpecSaver.new opts.each do |opt,arg| s.all if ( opt == "--all" ) s.branches if ( opt == "--branches" ) s.clients if ( opt == "--clients" ) s.config if ( opt == "--config" ) s.depots if ( opt == "--depots" ) s.groups if ( opt == "--groups" ) s.jobs if ( opt == "--jobs" ) s.labels if ( opt == "--labels" ) s.users if ( opt == "--users" ) s.debug( arg.empty? ? 1 : arg.to_i ) if ( opt == "--debug" ) end croak_syntax unless ( s.have_flags? ) s.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. |