P4.rb #1

  • //
  • guest/
  • perforce_software/
  • p4ruby/
  • main/
  • lib/
  • P4.rb
  • View
  • Commits
  • Open Download .zip Download (11 KB)
#*******************************************************************************
# 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 <[email protected]> or <[email protected]>
#*******************************************************************************

#*******************************************************************************
#* 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
#31 14719 tjuricek Mirroring the current state of the workshop.

From this point, we will try to rebuild the history of p4-ruby in the workshop from this codeline.
#30 14718 tony Make P4Ruby aware that a charset of "none" means no charset at all.


Previously, using a P4Ruby >= 2014.2 against a server at least as new
could result in getting 'Unicode clients require a unicode-enabled server'
errors in appropriately. This was because we were treating 'none' as
a character set and enabling unicode mode on the client with that as
the charset to use for file content.

With the advent of .p4enviro files, this problem has been cropping up
frequently as that is the default value supplied when talking to a non-unicode
server.

With this change, we now detect 'none' and use it to disable translation
as the user, and .p4enviro file no doubt intended.
#29 14716 tjuricek 2014.2.0.pre5 - Clearing the charset (calling ClientApi::SetTrans(0)) if the charset property is nil or none during construction.

Changed README to use the correct incantation of --with-p4api-dir. Additionally, fixed an issue using that option in lieu of the ftp.perforce.com download.

Also, removing any embedded distribution of the p4api. RubyGems sets a strict file size limit, which means we won't be able to distribute the C++ API embedded through their site.

Imported from Git
 Author: Tristan Juricek <[email protected]> 1423518911 -0800
 Committer: Tristan Juricek <[email protected]> 1423518911 -0800
 sha1: 90ef9f83b0f53ac88223636c0ad653ab5b00e528
 push-state: complete
 parent-changes: d10c4cae76976e7ca616cf17e116c9a16994236a=[999733]
#28 14711 tjuricek Clear the charset setting by setting it to nil in ruby-land if the charset is detected to be 'none'.

If the user happens to have set the value to 'none' in a config file, or it's otherwise specified, it appears the underlying charset detection mechanism in the C++ API treats it as an encoding. So, by setting it to 'nil' directly in the ruby code, we'll make the correct call to SetCharset(0) as the user probably intends.

P4RUBY-179

Imported from Git
 Author: Tristan Juricek <[email protected]> 1423002926 -0800
 Committer: Tristan Juricek <[email protected]> 1423002926 -0800
 sha1: f7591d8f78483b9872504cc77e1e193b3fae7419
 push-state: incomplete
 parent-changes: 9ce17f3ec56131a622187697d628bbb46263a30e=[998344]
#27 14703 tjuricek Set up call to 'rake cross native gem' as the main build step now.

This utilizes the 'cross compile' feature where we can build multiple versions of ruby under one gem package, though it's really going to be used for 'direct compilation'. The following variables need to be set to run the tools/build_p4ruby.sh now:

* RUBY_USE_VERSION - Should be the rvm version with the gem for publishing to geminabox
* RUBY_CC_VERSION - The colon (:) delimited string of ruby versions to build - each version should be installed via RVM and registered in ~/.rake_compiler/config.yml
* P4RUBY_CROSS_PLATFORM - The platform ID we are building on, needs to be set, e.g., 'x86_64-linux', 'i686-linux', 'x86_64-darwin-11'. Note that OS X uses the darwin 'version'.

P4RUBY-176

Imported from Git
 Author: Tristan Juricek <[email protected]> 1421184895 -0800
 Committer: Tristan Juricek <[email protected]> 1421184895 -0800
 sha1: 1e9e81f882ba0584a7bc66a48637a6576aee7cb9
 push-state: complete
 parent-changes: 50a9ca3b8ecb1e77ba823cbde20a7c89bd731d86=[984317]
#26 14682 Git Fusion Git Fusion branch management

Imported from Git
 ghost-of-change-num: 960958
 ghost-of-sha1: 005052ae424bd69f426f7209e741ca1c8c3253c7
 ghost-precedes-sha1: ad052c71a568ef12165e143a6866ad9ceffbb4a1
 parent-branch: None@960958
 push-state: incomplete
#25 14679 tony Fix p4ruby test run so it also includes the current directory in the
library search path. This helps us pick up the P4.so we're testing.

Also fixed some minor gripes and backed out a previous change that
obviously didn't work under 1.8
#24 14676 tony Rework P4Ruby unit tests so they they are actually units.
Now the
test order is irrelevant as all tests are standalone. Tidied up
the code formatting as well and removed all the tabs from the ruby
sources files. Added a modeline for vim users so consistent
indentation will be used.
#23 14652 jmistry YAC to P4#run_tickets

Change in format to return an array of keyed hashes.

For example:
    {
            'Ticket' => 'D6EBA4584B635E19EA579E33EB0195DA',
            'User' => 'pallen',
            'Host' => 'localhost:2003'
    }
#22 14650 jmistry Progress indicator for P4Ruby.

There is a new class P4::Progress that is set up to be subclassed by a user.
It has the following interface and P4Ruby expects the following class methods to
be defined by the user (even if it's an empty implementation):

    class Progress:
        def init(type)
        end

        def description(description, units )
        end

        def total( total )
        end

        def update( position )
        end

        def done( fail )
        end
    end

Users need to create a subclass of P4::Progress and assign an instance to P4
to enable the progress indicator:

class MyProgress < P4::Progress
    def update(pos):
       # do something with the value here

    # other methods

p4 = P4.new
p4.progress = MyProgress.new

New feature to be documented in the release notes.
#21 14648 jmistry Follow-on to change 500964

Forgot to submit unit test updates.  Also, instead of returning nil for a directory or non-existent file, it seems more consistent to return an empty array.
#20 14647 jmistry rename to P4#run_tickets

Also, instead of each line being an element in the array, the array contains
a single Hash, with each ticket entry as a separate element in the hash.
#19 14645 jmistry Ensure iterator args are passed on

Forgot to pass on any arguments that the user may have supplied to their call
to 'each_<spec>'.  Fixed this and also added a test for it.
#18 14644 jmistry Generic each_* methods

Simple method to iterate over a particular type of spec
This is a convenient wrapper for the pattern:
    clients = p4.run_clients
    clients.each do
    |c|
        client = p4.fetch_client( c['client'] )
        <do something with spec>
    end

NOTE:    It's not possible to implicitly pass a block to a
delegate method, so I've implemented it here directly.  Could use
Proc.new.call, but it looks like there is a serious performance
impact with that method.

job056520 : spec iterator implementation
#17 14643 jmistry Implement P4#tickets for P4Ruby

P4#tickets checks P4#ticket_file, if it's a directory or does not exist it just
returns 'nil'.  Otherwise it iterates over the file and inserts each line into
an array of hashes.  Each hash is a single ticket entry:

    key: ticket key (hostname:port=user)
    value: ticket value

Update unit test to test new functionality.
#16 14605 jmistry Fix P4.run_submit( P4::Spec )

Ruby 1.9 changed Array.flatten, so that it called 'to_ary'.  This
caused problems for P4Ruby (with Ruby 1.9) and would cause
P4.run_submit, P4.run_shelve, etc to fail with:

  `method_missing': undefined method `to_ary' for object of class P4::Spec (RuntimeError)

if it was passed a P4::Spec object as an argument.

P4::Spec now checks (in 'method_missing') if it's trying to run
'to_ary', and it raises 'NoMethodError' if it is.
#15 14593 Sven Erik Knop P4Ruby implementation of OutputHandler.
Also, debug is now treated as a normal attribute and can be read.

Test cases still missing.

New functionality, to be documented in release notes.
#14 14585 Sven Erik Knop Added P4#run_shelve and P4#delete_shelve as special cases - similar to P4Python.
P4#run_shelve allows passing in of a hash, assumed to be a changelist (i.e
shelvelist) spec, similar to P4#run_submit.
P4#delete_shelve adds an extra "-c" to the arguments before the shelve list number
if "-c" is not already in the argument list.

User-visible changes to be documented in the release notes.
#13 14579 tony Make new class P4::Message for returning Error objects
to the user. Currently handles errors and warnings, but
could potentially be used for output too (might bloat
people's code though).

Essentially, if you're using a 2010.2 or later client,
or if you've set your api_level to 68 or higher, the
P4#errors and P4#warnings arrays will be populated
with P4::Message objects instead of strings. Users of
older API's, or those who set their api_level to 67
or lower in their scripts will get the old behaviour.

P4::Message objects have the following methods:

  severity() - returns the severity
  generic()  - returns the generic code
  to_s()     - converts the message to a string
  inspect()  - returns a string showing the message details.

User-visible enhancement documented in p4rubynotes.txt
#12 14565 tony Add P4#inspect method so you get some information about the object
when using irb.

User-visible change, not documented anywhere (unsupported).
#11 14541 tony Copyright notice housekeeping: update all notices to 2008, and
correct start date from 1997 to 2001 when P4Ruby was first released
from the public depot.

No functional change
#10 14529 tony Pull 2007.3 p4-ruby changes back to main.

Integration only change
#9 14521 tony Update copyright notices in all applicable P4Ruby files.
#8 14515 tony Make P4::Revision attributes numbers if they're entirely
composed of numbers (saves the user doing it).
#7 14514 tony Rename P4Ruby's P4DepotFile, P4Revision, and P4Integration classes
to be P4::DepotFile, P4::Revision and P4::Integration respectively.

Also changed P4Ruby's P4::Revision class to store arbitrary
attributes of revisions, which should help with server compatibility.
Note that P4Perl's already did that, so this just ports those changes
over.

Also, changed the method name in P4Perl's class from

    P4::Revision::Set() to
    P4::Revision::SetAttribute()

as that's a better name for it. P4Ruby's equivalent is called

    P4::Revision#set_attribute()

User-visible change to be documented in Scripting Interfaces Guide
#6 14506 tony Fix old call to exception_level?
that should have been changed
to 'exception_level' a while back.

Bug fix to previous unreleased change
#5 14493 tony Turn on tagged mode by default, and make parse_forms mode
always on (it only works when tagged mode is on too). Now
users can turn tagged mode off if they want to by calling:

    p4.tagged = false

and turn it back on by calling:

    p4.tagged = true

You can test the current state of affairs using:

    p4.tagged?

User-visible change to be documented in Scripting Interface Guide
#4 14492 tony Rename P4#input to P4#input=, and fix all pre-existing calls.
The
main existing callers are p4.run_password, p4.run_submit and
and p4.run_login. There was also a bug with p4.run_password() which
meant that you couldn't change a user's password if they hadn't
already set one. Fixed that while I was there and added a test
case to check that password setting, use and clearing works.
#3 14490 tony Rename P4#submit_spec to P4#run_submit.
So now run_submit
can take a variety of arguments, but if any of them are Hash
or P4::Spec objects then they get converted into strings and
a '-i' gets added to the submit args.
#2 14483 tony Method name rationalisation, part 1.
All the methods with names ending
with '?' that do not return booleans now have the '?' dropped. This
will be used to ensure consistency across the scripting interfaces.

Added P4#prog() -> <string> to partner P4#prog=( <string> )

Removed some redundant aliases that we're dropping support for in
future versions
#1 14480 tony Add P4Ruby 1.5944 to main as start-point for the first
productized release of P4Ruby