#******************************************************************************* # Copyright (c) 1997-2004, 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. #******************************************************************************* #******************************************************************************* #* Ruby interface to the Perforce SCM System #* Tony Smith <tony@perforce.com> or <tony@smee.org> #******************************************************************************* #******************************************************************************* #* Supporting classes #******************************************************************************* # # P4Integration objects hold details about the integrations that have # been performed on a particular revision. Used primarily with the # P4Revision class # class P4Integration def initialize( how, file, srev, erev ) @how = how @file = file @srev = srev @erev = erev end attr_reader :how, :file, :srev, :erev end # # Each P4Revision object holds details about a particular revision # of a file. It may also contain the history of any integrations # to/from the file # class P4Revision def initialize( depotFile ) @depot_file = depotFile @integrations = Array.new @revno = @change = @action = @type = @time = @user = @client = @desc = nil end attr_reader :depot_file attr_accessor :revno, :change, :action, :type, :time, :user, :client, :desc, :integrations def integration( how, file, srev, erev ) rec = P4Integration.new( how, file, srev, erev ) @integrations.push( rec ) return rec end def each_integration @integrations.each { |i| yield( i ) } end end # # Each DepotFile entry contains details about one depot file. # class P4DepotFile def initialize( name ) @depot_file = name @revisions = Array.new @headAction = @head_type = @head_time = @head_rev = @head_change = nil end attr_reader :depot_file, :revisions attr_accessor :head_action, :head_type, :head_time, :head_rev, :head_change def new_revision r = P4Revision.new( @depot_file ) @revisions.push( r ) return r end def each_revision @revisions.each { |r| yield( r ) } end end #******************************************************************************* #* P4 class #******************************************************************************* # # Get the bulk of the definition of the P4 class from the API interface # require "P4.so" # # Add the extra's written purely in ruby. # class P4 # # Named constants for the exception levels. Note they are cumulative, # so RAISE_ALL includes RAISE_ERRORS (as you'd expect). # RAISE_NONE = 0 RAISE_ERRORS = 1 RAISE_ALL = 2 # # Named values for merge actions. Values taken from clientmerge.h in # the Perforce API # MERGE_SKIP = 1 MERGE_ACCEPT_MERGED = 2 MERGE_ACCEPT_EDIT = 3 MERGE_ACCEPT_THEIRS = 4 MERGE_ACCEPT_YOURS = 5 def method_missing( m, *a ) # Generic run_* methods if ( m.to_s =~ /^run_(.*)/ ) return self.run( $1, a ) # Generic fetch_* methods elsif ( m.to_s =~ /^fetch_(.*)/ ) return self.run( $1, "-o", a ).shift # Generic save_* methods elsif ( m.to_s =~ /^save_(.*)/ ) if ( a.length == 0 ) raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller) end self.input( a.shift ) return self.run( $1, "-i", a ) # Generic delete_* methods elsif ( m.to_s =~ /^delete_(.*)/ ) if ( a.length == 0 ) raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller) end return self.run( $1, "-d", a ) # Generic parse_* methods elsif ( m.to_s =~ /^parse_(.*)/ ) if ( a.length != 1 ) raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller) end return self.parse_spec( $1, a.shift ) # Generic format_* methods elsif ( m.to_s =~ /^format_(.*)/ ) if ( a.length != 1 ) raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller) end return self.format_spec( $1, a.shift ) # That's all folks! else raise NameError, "No such method #{m.to_s} in class P4", caller end end # Aliases alias cwd cwd= alias client client= alias host host= alias port port= alias user user= alias debug debug= alias exception_level exception_level= # # Simple interface for submitting. Just supply the change form either # as a text string, or as the edit hash from a previous p4.fetch_change() # or p4.run_change( "-o" ). # def submit_spec( spec ) self.input( spec ) return self.run_submit( "-i" ) end # # Simple interface for using "p4 login" # def run_login( *args ) self.input( self.password? ) return self.run( "login", args ) end def run_resolve( *args ) if( block_given? ) self.run( "resolve", args ) do |default| yield( default ) end else self.run( "resolve", args ) end end # # Interface for changing the user's password. Supply the old password # and the new one. # def run_password( oldpass, newpass ) self.input( [ oldpass, newpass, newpass ] ) self.run( "password" ) end # # The following methods convert the standard output of some common # Perforce commands into more structured form to make using the # data easier. # # (Currently only run_filelog is defined. More to follow) # # run_filelog: convert "p4 filelog" responses into objects with useful # methods # # Requires tagged output to be of any real use. If tagged output it not # enabled then you just get the raw data back # def run_filelog( *args ) raw = self.run( 'filelog', args.flatten ) raw.collect do |h| if ( ! h.kind_of?( Hash ) ) h else df = P4DepotFile.new( h[ "depotFile" ] ) h[ "rev" ].each_index do |n| # If rev is nil, there's nothing here for us next unless h[ "rev" ][ n ] # Create a new revision of this file ready for populating r = df.new_revision # Populate the base attributes of each revision r.revno = h[ "rev" ][ n ].to_i r.change = h[ "change" ][ n ].to_i r.action = h[ "action" ][ n ] r.type = h[ "type" ][ n ] r.time = Time.at( h[ "time" ][ n ].to_i ) r.user = h[ "user" ][ n ] r.client = h[ "client" ][ n ] r.desc = h[ "desc" ][ n ] # Now if there are any integration records for this revision, # add them in too next unless ( h[ "how" ] ) next unless ( h[ "how" ][ n ] ) h[ "how" ][ n ].each_index do |m| how = h[ "how" ][ n ][ m ] file = h[ "file" ][ n ][ m ] srev = h[ "srev" ][ n ][ m ] erev = h[ "erev" ][ n ][ m ] srev.gsub!( /^#/, "" ) erev.gsub!( /^#/, "" ) srev = ( srev == "none" ? 0 : srev.to_i ) erev = ( erev == "none" ? 0 : erev.to_i ) r.integration( how, file, srev, erev ) end end df end end end # # The P4::Spec class holds the fields in a Perforce spec # class Spec < Hash def initialize( fieldmap = nil ) @fields = fieldmap end # # Override the default assignment method. This implementation # ensures that any fields defined are valid ones for this type of # spec. # def []=( key, value ) if( self.has_key?( key ) || @fields == nil ) super( key, value ) elsif( @fields.has_key?( key.downcase ) ) super( @fields[ key.downcase ], value ) else raise( P4Exception, "Invalid field: #{key}" ) end end # # Return the list of the fields that are permitted in this spec # def permitted_fields @fields.values end # # Implement accessor methods for the fields in the spec. The accessor # methods are all prefixed with '_' to avoid conflicts with the Hash # class' namespace. This is a little ugly, but we gain a lot by # subclassing Hash so it's worth it. # def method_missing( m, *a ) k = m.to_s.downcase if( k[ 0..0 ] != "_" ) raise( RuntimeError, "undefined method `#{m.to_s}' for object of " + "class #{self.class.to_s}" ) end k = k[ 1..-1 ] if( k =~ /(.*)=$/ ) if( a.length() == 0 ) raise( P4Exception, "Method P4##{m} requires an argument" ); end k = $1 if( @fields == nil || @fields.has_key?( k ) ) return self[ @fields[ k ] ] = a.shift end elsif( self.has_key?( m.to_s ) ) return self[ m.to_s ] elsif( @fields.has_key?( k ) ) return self[ @fields[ k ] ] end raise( P4Exception, "Invalid field: #{$1}" ) end end # # Allow the user to run commands at a temporarily altered exception level. # Pass the new exception level desired, and a block to be executed at that # level. # def at_exception_level( level ) return self unless block_given? old_level = self.exception_level? self.exception_level = level begin yield( self ) ensure self.exception_level = old_level end self end #*************************************************************************** #* P4MergeInfo class #*************************************************************************** class MergeInfo def initialize( base, yours, theirs, merged, hint ) @base = base @yours = yours @theirs = theirs @merged = merged @hint = hint end attr_reader :base, :yours, :theirs, :merged, :hint end end
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#23 | 5791 | Tony Smith |
Add experimental support for passing a block to P4#run_resolve. The block is passed a P4::MergeData object encapsulating the context of each merge performed. The block should evaluate to a string indicating the desired result of the merge: 'ay', 'at', 'am', 's', etc. The P4::MergeData object contains information about the files involved in the merge and can invoke an external merge tool. This is still experimental at this stage so the interface may change as it evolves. |
||
#22 | 5339 | Tony Smith |
Bug fix: A typo in change 5335 introduced a bug. This just corrects the typo. Thanks to Dean Wampler <dean@aspectprogramming.com> for spotting it. |
||
#21 | 5335 | Tony Smith |
Bug fix: P4::Spec#[]= method didn't work properly when constructed without a fieldmap. In that case, the user might see this error: P4.rb:292:in `[]=': undefined method `[]' for nil:NilClass (NoMethodError) This change corrects the logic and the method now works properly. |
||
#20 | 4940 | Tony Smith |
Add (undoc'd) support for enabling/disabling tagged mode on a per-command basis. Also fixed a minor typo which was rendering an error message less useful than intended. |
||
#19 | 4830 | Tony Smith |
Add named constants for P4Ruby's exception levels. The valid levels are: P4::RAISE_NONE -- No exceptions P4::RAISE_ERRORS -- Exceptions on errors only P4::RAISE_ALL -- Exceptions on errors and warnings Also added P4#at_exception_level( level ) { ... } method to allow you to run a block of code at a different exception level and revert to the previous level when the block completes. Thanks to Johan Nilsson. Some doc tidying along with the docs for the features above. |
||
#18 | 4753 | Tony Smith |
Add support for executing commands which prompt the user for input more than once during their execution. A perfect example is 'p4 password' which prompts the user three times. This works by allowing P4#input() to take an array argument. Each time Perforce prompts the user (by calling ClientUserRuby::Prompt()), the array is shifted by one and the first value in the array is passed to Perforce. Thus, to change your password a three-element array is needed comprising of your old password, and the new password twice. To make this a little easier on the eye, this change also includes a thin wrapper called P4#run_password() which takes simply the old password and the new password and constructs a suitable input array. This change also includes docs for the above, and docs for P4#run_filelog() which were found to be missing. |
||
#17 | 4680 | Tony Smith |
Make P4Ruby return new P4::Spec objects instead of plain old hashes when parse_forms mode is in use. A P4::Spec object is derived from Hash so should be backwards compatible with previous code. P4::Spec provides limited fieldname validation on forms and accessor methods for quick and easy access to the fields in the form. The accessor methods are all prefixed with '_' to avoid colliding with methods from the Hash parent class. This is a little ugly, but deriving from hash is a big win, so it's worth it. This change also fixes a minor bug found along the way. Spec parsing and formatting wouldn't work with labels, branches, depots and groups unless you'd previously run a P4::fetch_label( <label> ), P4::fetch_branch( <branch> ) etc. etc. This is because the spec parsing code internally runs one of these commands in order to grab the specdef from the server but it wasn't providing a spec name. i.e. it was using 'p4 client -o' and assuming that this would work for other types of spec too. It does, but not for all spec types. So, now the spec parsing code will use a bogus name for the spec types that require it. |
||
#16 | 4651 | Tony Smith |
Add format_spec() method and format_* shortcuts to make it easy to convert a spec in a hash back to its string form without sending it to the server. |
||
#15 | 4593 | Tony Smith |
Add support for 'p4 login' to P4Ruby per request from Robert Cowham. New installer to follow. |
||
#14 | 4261 | Tony Smith |
Add support for parsing arbitrary specs from strings in Ruby space. Useful with spec depots. You might obtain the spec by running a "p4 print -q" against a file in a spec depot, but want to parse it into a Ruby hash. i.e. p4 = P4.new p4.parse_forms # Required! p4.connect buf = p4.run_print( "-q", "//specs/client/myclient" ) spec = p4.parse_client( buf ) # Or equivalently spec = p4.parse_spec( "client", buf ) |
||
#13 | 4251 | Tony Smith |
Add support for delete_* methods to P4Ruby so you can now say: p4.delete_client( "clientname" ) instead of the old: p4.run_client( "-d", "clientname" ) Just some shorthand. Also some small changes to the setup script to allow for people who install the Perforce API in a system-wide location such as /usr/include/p4/ /usr/lib/p4/ Now the installer takes --apilibdir= and --apiincludedir= params as well as the old --apidir= |
||
#12 | 4159 | Tony Smith |
Bug fix. P4#save_* methods previously only accepted one argument, now, you can pass extras. This allows you to run commands like: p4.save_client( spec, "-f" ) # i.e. "p4 client -i -f" Note that you must still pass the spec as the first argument, and the flags must follow. |
||
#11 | 4157 | Tony Smith |
Copyright notice update. No functional change |
||
#10 | 2437 | Tony Smith |
Minor nit fix. "raise interpreted as method call" warning no longer occurs. |
||
#9 | 2388 | Tony Smith |
Rework p4conf.rb script to improve the build process. There are now two mandatory parms and if you omit them you will be prompted for their values. --apidir <dir> - Path to the Perforce API --apiver <string> - API version string (e.g. 2002.2) I've also renamed the .so file from "P4api.so" to just P4.so and I've improved the self identification code to include the build flags and the API version to help with diagnosis. |
||
#8 | 1426 | Tony Smith |
Cleaned up the debug output a little. Introduced some debug levels so you can decide (roughly) what output you want to see. Level 1 shows command execution, connect and disconnect. Level 2 includes Level 1 and also shows the RPC callbacks as they happen. Level 3 includes 1 and 2 and also shows when Ruby garbage collection takes place. Converted all the simple methods of the form P4#meth( arg ) to aliases for P4#meth=. Added P4#debug= to complete the scheme. The P4#meth( arg ) forms are now deprecated. i.e. you should use: p4.user = "tony" and not: p4.user( "tony" ) It's just more Ruby-like. |
||
#7 | 1393 | Tony Smith |
Bug fix: Filelogs for files without any integration records were raising a NameError exception as the "how" tag was not defined. This change just looks before it leaps. |
||
#6 | 1323 | Tony Smith |
Added P4DepotFile, P4Revision and P4Integration classes to provide an easy to use way of examining responses from the server. Currently used only by P4#run_filelog (see below). To be enhanced in future versions. Added P4#run_filelog definition. Previously handled by method missing, now when used in tagged mode, the complex filelog response is returned as an array of P4DepotFile objects. Each P4DepotFile object has an array of P4Revision objects and each revision has an array of P4Integration objects. This structure makes it much easier to analyse the filelog data in a script. |
||
#5 | 1173 | Tony Smith |
Bug fix. Arguments to P4#fetch_* methods were not being passed to P4#run. This change builds on change 1168 and ensures that all args are always passed to the run method. |
||
#4 | 1166 | Tony Smith |
Followup to previous change. Simplify the interface to getting results/errors and warnings. No need for the P4Result class anymore so that's gone (though it's still there as a C++ class because it's useful) and so is P4#result. Now you get your errors/warnings and results using P4#errors, P4#warnings and P4#output all of which return arrays. |
||
#3 | 1164 | Tony Smith |
Reworked exception handling (hopefully for the last time) in P4/Ruby. Now exceptions are raised on completion of Perforce commands if any errors or warnings were received as part of executing the command. This change also adds documentation, and indexes the Ruby interface off my main page. Bad form to combine so many changes in one changelist, but it's getting late and I want to get them submitted! |
||
#2 | 1052 | Tony Smith |
Add P4#submit_spec method to allow easy submitting. Just supply the changespec as either a String or as an edited hash returned by a previous call to P4#run( "change", "-o" ) The Ruby API now has functional parity with the Perl API. |
||
#1 | 1030 | Tony Smith |
P4.rb moved down one level to "lib" subdirectory. Clunky but it makes mkmf do the right thing on "make install". |
||
//guest/tony_smith/perforce/API/Ruby/main/P4.rb | |||||
#1 | 1027 | Tony Smith |
Rework structure slightly. What was P4.so is now called P4api.so and a new P4.rb module is added. P4api.so contains the raw bridge to the Perforce API whilst P4.rb contains extensions to it written purely in Ruby. Both define and extend the P4 class and P4.rb requires P4api.so so user code is unaffected. The intent is to give me a place to write Ruby code rather than having to work solely in C++. The first method added using this new structure is P4#method_missing, so now Perforce commands all appear as methods on the P4 object (prefixed by "run_" ). e.g p4 = P4.new p4.run_info() p4.run_users() p4.run_protect( "-o" ) This change also adds support for shortcut methods for form editing. fetch* and save* methods have been added as shortcuts for "p4 <cmd> -o" and "p4 <cmd> -i" respectively. For example: p4 = P4.new p4.parse_forms() client_spec = p4.fetch_client() client_spec[ "Owner" ] = tony p4.save_client( client_spec ) Note that unlike the run* methods, these accessor methods do not return an array, but return a single result - a string normally, or a hash in parse_forms mode. Ruby's arcane build/install system means that you have to install the P4.rb file yourself. "make install" doesn't take care of it. |