p4ruby 2022.1.2359956-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
data/lib/P4.rb ADDED
@@ -0,0 +1,688 @@
1
+ #*******************************************************************************
2
+ # vim:ts=2:sw=2:et:
3
+ # Copyright (c) 2001-2008, Perforce Software, Inc. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ #
11
+ # 2. Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ # ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
19
+ # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+ #*******************************************************************************
26
+
27
+ #*******************************************************************************
28
+ #* Ruby interface to the Perforce SCM System
29
+ #*******************************************************************************
30
+
31
+ #*******************************************************************************
32
+ #* P4 class
33
+ #*******************************************************************************
34
+
35
+ require 'P4/version'
36
+
37
+ #
38
+ # Get the bulk of the definition of the P4 class from the API interface.
39
+ #
40
+ # If this is our precompiled gem, the shared library will lie underneath a
41
+ # a version specific folder.
42
+ #
43
+ begin
44
+ RUBY_VERSION =~ /(\d+\.\d+)/
45
+ require "#{$1}/P4.so"
46
+ rescue LoadError
47
+ require 'P4.so'
48
+ end
49
+
50
+ #
51
+ # Add the extra's written purely in ruby.
52
+ #
53
+ class P4
54
+
55
+ #
56
+ # Named constants for the exception levels. Note they are cumulative,
57
+ # so RAISE_ALL includes RAISE_ERRORS (as you'd expect).
58
+ #
59
+ RAISE_NONE = 0
60
+ RAISE_ERRORS = 1
61
+ RAISE_ALL = 2
62
+
63
+ #
64
+ # Named values for merge actions. Values taken from clientmerge.h in
65
+ # the Perforce API
66
+ #
67
+ MERGE_SKIP = 1
68
+ MERGE_ACCEPT_MERGED = 2
69
+ MERGE_ACCEPT_EDIT = 3
70
+ MERGE_ACCEPT_THEIRS = 4
71
+ MERGE_ACCEPT_YOURS = 5
72
+
73
+ # Named values for generic error codes returned by
74
+ # P4::Message#generic
75
+
76
+ EV_NONE = 0 # misc
77
+
78
+ # The fault of the user
79
+
80
+ EV_USAGE = 0x01 # request not consistent with dox
81
+ EV_UNKNOWN = 0x02 # using unknown entity
82
+ EV_CONTEXT = 0x03 # using entity in wrong context
83
+ EV_ILLEGAL = 0x04 # trying to do something you can't
84
+ EV_NOTYET = 0x05 # something must be corrected first
85
+ EV_PROTECT = 0x06 # protections prevented operation
86
+
87
+ # No fault at all
88
+
89
+ EV_EMPTY = 0x11 # action returned empty results
90
+
91
+ # not the fault of the user
92
+
93
+ EV_FAULT = 0x21 # inexplicable program fault
94
+ EV_CLIENT = 0x22 # client side program errors
95
+ EV_ADMIN = 0x23 # server administrative action required
96
+ EV_CONFIG = 0x24 # client configuration inadequate
97
+ EV_UPGRADE = 0x25 # client or server too old to interact
98
+ EV_COMM = 0x26 # communications error
99
+ EV_TOOBIG = 0x27 # not even Perforce can handle this much
100
+
101
+ # Named values for error severities returned by
102
+ # P4::Message#severity
103
+ E_EMPTY = 0 # nothing yet
104
+ E_INFO = 1 # something good happened
105
+ E_WARN = 2 # something not good happened
106
+ E_FAILED = 3 # user did something wrong
107
+ E_FATAL = 4 # system broken -- nothing can continue
108
+
109
+ # OutputHandler return values constants
110
+
111
+ REPORT = 0
112
+ HANDLED = 1
113
+ CANCEL = 2
114
+
115
+ # Client progress 'done' state
116
+ PROG_NORMAL = 0
117
+ PROG_DONE = 1
118
+ PROG_FAILDONE = 2
119
+ PROG_FLUSH = 3
120
+
121
+ # SSO Handler return values constants
122
+ SSO_PASS = 0 # SSO succeeded (result is an authentication token)
123
+ SSO_FAIL = 1 # SSO failed (result will be logged as error message)
124
+ SSO_UNSET = 2 # Client has no SSO support
125
+ SSO_EXIT = 3 # Stop login process
126
+ SSO_SKIP = 4 # Fall back to default P4API behavior
127
+
128
+ # Mappings for P4#each_<spec>
129
+ # Hash of type vs. key
130
+ SpecTypes = {
131
+ "clients" => ["client", "client"],
132
+ "labels" => ["label", "label"],
133
+ "branches" => ["branch", "branch"],
134
+ "changes" => ["change", "change"],
135
+ "streams" => ["stream", "Stream"],
136
+ "jobs" => ["job", "Job"],
137
+ "users" => ["user", "User"],
138
+ "groups" => ["group", "group"],
139
+ "depots" => ["depot", "name"],
140
+ "servers" => ["server", "Name"],
141
+ }
142
+
143
+ def method_missing( m, *a )
144
+
145
+ # Generic run_* methods
146
+ if ( m.to_s =~ /^run_(.*)/ )
147
+ return self.run( $1, a )
148
+
149
+ # Generic fetch_* methods
150
+ elsif ( m.to_s =~ /^fetch_(.*)/ )
151
+ return self.run( $1, "-o", a ).shift
152
+
153
+ # Generic save_* methods
154
+ elsif ( m.to_s =~ /^save_(.*)/ )
155
+ if ( a.length == 0 )
156
+ raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller)
157
+ end
158
+ self.input = a.shift
159
+ return self.run( $1, "-i", a )
160
+
161
+ # Generic delete_* methods
162
+ elsif ( m.to_s =~ /^delete_(.*)/ )
163
+ if ( a.length == 0 )
164
+ raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller)
165
+ end
166
+ return self.run( $1, "-d", a )
167
+
168
+ # Generic parse_* methods
169
+ elsif ( m.to_s == "parse_forms" )
170
+ raise( NoMethodError, "undefined method 'P4#parse_forms'", caller )
171
+ elsif ( m.to_s =~ /^parse_(.*)/ )
172
+ if ( a.length != 1 )
173
+ raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller)
174
+ end
175
+ return self.parse_spec( $1, a.shift )
176
+
177
+ # Generic format_* methods
178
+ elsif ( m.to_s =~ /^format_(.*)/ )
179
+ if ( a.length != 1 )
180
+ raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller)
181
+ end
182
+ return self.format_spec( $1, a.shift )
183
+
184
+ #
185
+ # Generic each_* methods
186
+ # Simple method to iterate over a particular type of spec
187
+ # This is a convenient wrapper for the pattern:
188
+ # clients = p4.run_clients
189
+ # clients.each do
190
+ # |c|
191
+ # client = p4.fetch_client( c['client'] )
192
+ # <do something with client>
193
+ # end
194
+ #
195
+ # NOTE: It's not possible to implicitly pass a block to a
196
+ # delegate method, so I've implemented it here directly. Could use
197
+ # Proc.new.call, but it looks like there is a serious performance
198
+ # impact with that method.
199
+ #
200
+ elsif ( m.to_s =~ /^each_(.*)/ )
201
+ raise( P4Exception, "No such method P4##{m.to_s}", caller) unless SpecTypes.has_key?( $1 )
202
+ raise( P4Exception, "Method P4##{m.to_s} requires block", caller) unless block_given?
203
+ specs = self.run( $1, a )
204
+ cmd = SpecTypes[ $1 ][0].downcase
205
+ key = SpecTypes[ $1 ][1]
206
+
207
+ specs.each{
208
+ |spec|
209
+ spec = self.run( cmd, "-o", spec[key] ).shift
210
+ yield spec
211
+ }
212
+ return specs
213
+
214
+ # That's all folks!
215
+ else
216
+ raise NameError, "No such method #{m.to_s} in class P4", caller
217
+ end
218
+ end
219
+
220
+ #
221
+ # Simple interface for submitting. If any argument is a Hash, (or subclass
222
+ # thereof - like P4::Spec), then it will be assumed to contain the change
223
+ # form. All other arguments are passed on to the server unchanged.
224
+ #
225
+ def run_submit( *args )
226
+ form = nil
227
+ nargs = args.flatten.collect do
228
+ |a|
229
+ if( a.kind_of?( Hash ) )
230
+ form = a
231
+ nil
232
+ else
233
+ a
234
+ end
235
+ end.compact
236
+
237
+ if( form )
238
+ self.input = form
239
+ nargs.push( "-i" )
240
+ end
241
+ return self.run( "submit", nargs )
242
+ end
243
+
244
+ #
245
+ # Simple interface for shelving. Same rules as for submit apply
246
+
247
+ def run_shelve( *args )
248
+ form = nil
249
+ nargs = args.flatten.collect do
250
+ |a|
251
+ if( a.kind_of?( Hash ) )
252
+ form = a
253
+ nil
254
+ else
255
+ a
256
+ end
257
+ end.compact
258
+
259
+ if( form )
260
+ self.input = form
261
+ nargs.push( "-i" )
262
+ end
263
+ return self.run( "shelve", nargs )
264
+ end
265
+
266
+ def delete_shelve( *args )
267
+ if( ! args.include?( "-c" ) )
268
+ args.unshift( "-c")
269
+ end
270
+ return self.run( "shelve", "-d", args)
271
+ end
272
+
273
+ #
274
+ # Simple interface for using "p4 login"
275
+ #
276
+ def run_login( *args )
277
+ self.input = self.password
278
+ return self.run( "login", args )
279
+ end
280
+
281
+ def run_resolve( *args )
282
+ if( block_given? )
283
+ self.run( "resolve", args ) do
284
+ |default|
285
+ yield( default )
286
+ end
287
+ else
288
+ self.run( "resolve", args )
289
+ end
290
+ end
291
+
292
+ #
293
+ # Simple interface to 'p4 tickets'
294
+ #
295
+ def run_tickets
296
+ path = self.ticket_file
297
+ # return an empty array if the file doesn't exist
298
+ # or is a directory.
299
+ results = Array.new
300
+ re = Regexp.new( /([^=]*)=(.*):([^:]*)$/ )
301
+ if( File.exist?( path ) and !File.directory?( path ) )
302
+ File.open( path ) do
303
+ |file|
304
+ file.each_line do
305
+ |line|
306
+ res = re.match( line )
307
+ if( res )
308
+ tickets = { 'Host' => res[1], 'User' => res[2], 'Ticket' => res[3] }
309
+ results.push( tickets )
310
+ end
311
+ end
312
+ end
313
+ end
314
+ return results
315
+ end
316
+
317
+ #
318
+ # Interface for changing the user's password. Supply the old password
319
+ # and the new one.
320
+ #
321
+ def run_password( oldpass, newpass )
322
+ if( oldpass && oldpass.length > 0 )
323
+ self.input = [ oldpass, newpass, newpass ]
324
+ else
325
+ self.input = [ newpass, newpass ]
326
+ end
327
+ self.run( "password" )
328
+ end
329
+
330
+ #
331
+ # The following methods convert the standard output of some common
332
+ # Perforce commands into more structured form to make using the
333
+ # data easier.
334
+ #
335
+ # (Currently only run_filelog is defined. More to follow)
336
+
337
+ #
338
+ # run_filelog: convert "p4 filelog" responses into objects with useful
339
+ # methods
340
+ #
341
+ # Requires tagged output to be of any real use. If tagged output it not
342
+ # enabled then you just get the raw data back
343
+ #
344
+ def run_filelog( *args )
345
+ raw = self.run( 'filelog', args.flatten )
346
+ raw.collect do
347
+ |h|
348
+ if ( ! h.kind_of?( Hash ) )
349
+ h
350
+ else
351
+ df = P4::DepotFile.new( h[ "depotFile" ] )
352
+ h[ "rev" ].each_index do
353
+ |n|
354
+
355
+ # If rev is nil, there's nothing here for us
356
+ next unless h[ "rev" ][ n ]
357
+
358
+ # Create a new revision of this file ready for populating
359
+ r = df.new_revision
360
+
361
+ h.each do
362
+ |key,value|
363
+ next unless( value.kind_of?( Array ) )
364
+ next unless value[ n ]
365
+ next if( value[ n ].kind_of?( Array ) )
366
+ # If the field is the revision time, convert it to a Time object
367
+ value[ n ] = Time.at( value[ n ].to_i ) if key == "time"
368
+ r.set_attribute( key, value[ n ] )
369
+ end
370
+
371
+ # Now if there are any integration records for this revision,
372
+ # add them in too
373
+ next unless ( h[ "how" ] )
374
+ next unless ( h[ "how" ][ n ] )
375
+
376
+ h[ "how" ][ n ].each_index do
377
+ |m|
378
+ how = h[ "how" ][ n ][ m ]
379
+ file = h[ "file" ][ n ][ m ]
380
+ srev = h[ "srev" ][ n ][ m ]
381
+ erev = h[ "erev" ][ n ][ m ]
382
+ srev.gsub!( /^#/, "" )
383
+ erev.gsub!( /^#/, "" )
384
+ srev = ( srev == "none" ? 0 : srev.to_i )
385
+ erev = ( erev == "none" ? 0 : erev.to_i )
386
+
387
+ r.integration( how, file, srev, erev )
388
+ end
389
+ end
390
+ df
391
+ end
392
+ end
393
+ end
394
+
395
+ #
396
+ # Allow the user to run commands at a temporarily altered exception level.
397
+ # Pass the new exception level desired, and a block to be executed at that
398
+ # level.
399
+ #
400
+ def at_exception_level( level )
401
+ return self unless block_given?
402
+ old_level = self.exception_level
403
+ self.exception_level = level
404
+ begin
405
+ yield( self )
406
+ ensure
407
+ self.exception_level = old_level
408
+ end
409
+ self
410
+ end
411
+
412
+ #
413
+ # Allow users to run commands using a specified handler.
414
+ # Pass a handler and the block that will be executed using this handler
415
+ # The handler will be reset to its previous value at the end of this block
416
+ #
417
+ def with_handler( handler )
418
+ return self unless block_given?
419
+ old_handler = self.handler
420
+ self.handler = handler
421
+ begin
422
+ yield( self )
423
+ ensure
424
+ self.handler = old_handler
425
+ end
426
+ self
427
+ end
428
+
429
+ #
430
+ # Show some handy information when using irb
431
+ #
432
+ def inspect
433
+ sprintf( 'P4: [%s] %s@%s (%s)',
434
+ self.port, self.user, self.client,
435
+ self.connected? ? 'connected' : 'not connected' )
436
+ end
437
+
438
+ #*****************************************************************************
439
+ # The P4::Spec class holds the fields in a Perforce spec
440
+ #*****************************************************************************
441
+ class Spec < Hash
442
+ def initialize( fieldmap = nil )
443
+ @fields = fieldmap
444
+ end
445
+
446
+ #
447
+ # Override the default assignment method. This implementation
448
+ # ensures that any fields defined are valid ones for this type of
449
+ # spec.
450
+ #
451
+ def []=( key, value )
452
+ if( self.has_key?( key ) || @fields == nil )
453
+ super( key, value )
454
+ elsif( @fields.has_key?( key.downcase ) )
455
+ super( @fields[ key.downcase ], value )
456
+ else
457
+ raise( P4Exception, "Invalid field: #{key}" )
458
+ end
459
+ end
460
+
461
+ #
462
+ # Return the list of the fields that are permitted in this spec
463
+ #
464
+ def permitted_fields
465
+ @fields.values
466
+ end
467
+
468
+ #
469
+ # Implement accessor methods for the fields in the spec. The accessor
470
+ # methods are all prefixed with '_' to avoid conflicts with the Hash
471
+ # class' namespace. This is a little ugly, but we gain a lot by
472
+ # subclassing Hash so it's worth it.
473
+ #
474
+ def method_missing( m, *a )
475
+ k = m.to_s.downcase
476
+
477
+ # Check if we're being asked for 'to_ary'. If so, raise 'NoMethodError'.
478
+ raise NoMethodError if( k == "to_ary" )
479
+
480
+ if( k[0..0] != "_" )
481
+ raise( RuntimeError,
482
+ "undefined method `#{m.to_s}' for object of " +
483
+ "class #{self.class.to_s}" )
484
+ end
485
+ k = k[ 1..-1 ]
486
+
487
+ if( k =~ /(.*)=$/ )
488
+ if( a.length() == 0 )
489
+ raise( P4Exception, "Method P4##{m} requires an argument" );
490
+ end
491
+
492
+ k = $1
493
+ if( @fields == nil || @fields.has_key?( k ) )
494
+ return self[ @fields[ k ] ] = a.shift
495
+ end
496
+ elsif( self.has_key?( m.to_s ) )
497
+ return self[ m.to_s ]
498
+ elsif( @fields.has_key?( k ) )
499
+ return self[ @fields[ k ] ]
500
+ end
501
+ raise( P4Exception, "Invalid field: #{$1}" )
502
+ end
503
+ end
504
+
505
+ #*****************************************************************************
506
+ #* P4::MergeInfo class
507
+ #*****************************************************************************
508
+
509
+ class MergeInfo
510
+ def initialize( base, yours, theirs, merged, hint )
511
+ @base = base
512
+ @yours = yours
513
+ @theirs = theirs
514
+ @merged = merged
515
+ @hint = hint
516
+ end
517
+
518
+ attr_reader :base, :yours, :theirs, :merged, :hint
519
+ end
520
+
521
+ #*****************************************************************************
522
+ # P4::Integration class
523
+ # P4::Integration objects hold details about the integrations that have
524
+ # been performed on a particular revision. Used primarily with the
525
+ # P4::Revision class
526
+ #*****************************************************************************
527
+ class Integration
528
+ def initialize( how, file, srev, erev )
529
+ @how = how
530
+ @file = file
531
+ @srev = srev
532
+ @erev = erev
533
+ end
534
+
535
+ attr_reader :how, :file, :srev, :erev
536
+ end
537
+
538
+ #*****************************************************************************
539
+ # P4::Revision class
540
+ # Each P4::Revision object holds details about a particular revision
541
+ # of a file. It may also contain the history of any integrations
542
+ # to/from the file
543
+ #*****************************************************************************
544
+
545
+ class Revision
546
+ def initialize( depotFile )
547
+ @depot_file = depotFile
548
+ @integrations = Array.new
549
+ @attributes = Hash.new
550
+ end
551
+
552
+ attr_reader :depot_file
553
+ attr_accessor :integrations
554
+
555
+ def integration( how, file, srev, erev )
556
+ rec = P4::Integration.new( how, file, srev, erev )
557
+ @integrations.push( rec )
558
+ return rec
559
+ end
560
+
561
+ def each_integration
562
+ @integrations.each { |i| yield( i ) }
563
+ end
564
+
565
+ def set_attribute( name, value )
566
+ name = name.downcase
567
+ if( value.to_s =~ /^\d+$/ )
568
+ @attributes[ name ] = value.to_i
569
+ else
570
+ @attributes[ name ] = value
571
+ end
572
+ end
573
+
574
+ # Define #type and #type= explicitly as they clash with the
575
+ # deprecated Object#type. As it is deprecated, this clash should
576
+ # disappear in time.
577
+ def type
578
+ @attributes[ 'type' ]
579
+ end
580
+
581
+ def type=( t )
582
+ @attributes[ 'type' ] = t
583
+ end
584
+
585
+ #
586
+ # Generic getters and setters for revision attributes.
587
+ #
588
+ def method_missing( m, *a )
589
+ k = m.to_s.downcase
590
+ if( k =~ /(.*)=$/ )
591
+ if( a.length() == 0 )
592
+ raise( P4Exception, "Method P4##{m} requires an argument" );
593
+ end
594
+ k = $1
595
+ @attributes[ k ] = a.shift
596
+ else
597
+ @attributes[ k ]
598
+ end
599
+ end
600
+ end
601
+
602
+ #*****************************************************************************
603
+ # P4::DepotFile class.
604
+ # Each DepotFile entry contains details about one depot file.
605
+ #*****************************************************************************
606
+ class DepotFile
607
+ def initialize( name )
608
+ @depot_file = name
609
+ @revisions = Array.new
610
+ @headAction = @head_type = @head_time = @head_rev = @head_change = nil
611
+ end
612
+
613
+ attr_reader :depot_file, :revisions
614
+ attr_accessor :head_action, :head_type, :head_time, :head_rev, :head_change
615
+
616
+ def new_revision
617
+ r = P4::Revision.new( @depot_file )
618
+ @revisions.push( r )
619
+ return r
620
+ end
621
+
622
+ def each_revision
623
+ @revisions.each { |r| yield( r ) }
624
+ end
625
+ end
626
+
627
+ #*****************************************************************************
628
+ # P4::OutputHandler class.
629
+ # Base class for all Handler classes that can be passed to P4::handler.
630
+ #*****************************************************************************
631
+ class OutputHandler
632
+ def outputStat(stat)
633
+ REPORT
634
+ end
635
+
636
+ def outputInfo(info)
637
+ REPORT
638
+ end
639
+
640
+ def outputText(text)
641
+ REPORT
642
+ end
643
+
644
+ def outputBinary(binary)
645
+ REPORT
646
+ end
647
+
648
+ def outputMessage(message)
649
+ REPORT
650
+ end
651
+ end
652
+
653
+ class ReportHandler < OutputHandler
654
+ def outputStat(stat)
655
+ p "stat:", stat
656
+ HANDLED
657
+ end
658
+
659
+ def outputInfo(info)
660
+ p "info:", info
661
+ HANDLED
662
+ end
663
+
664
+ def outputText(text)
665
+ p "text:", text
666
+ HANDLED
667
+ end
668
+
669
+ def outputBinary(binary)
670
+ p "binary:", binary
671
+ HANDLED
672
+ end
673
+
674
+ def outputMessage(message)
675
+ p "message:", message
676
+ HANDLED
677
+ end
678
+ end
679
+
680
+ #*****************************************************************************
681
+ # P4::SSOHandler class.
682
+ #*****************************************************************************
683
+ class SSOHandler
684
+ def authorize(vars, maxLength)
685
+ [ SSO_SKIP, "" ]
686
+ end
687
+ end
688
+ end # class P4
data/lib/P4.so ADDED
Binary file