p4ruby 2014.2.0.pre3-x86-linux

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,724 @@
1
+ /*******************************************************************************
2
+
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
+ /*******************************************************************************
29
+ * Name : p4clientapi.cc
30
+ *
31
+ * Author : Tony Smith <tony@perforce.com> or <tony@smee.org>
32
+ *
33
+ * Description : Ruby bindings for the Perforce API. Main interface to the
34
+ * Perforce API.
35
+ *
36
+ ******************************************************************************/
37
+ #include <ruby.h>
38
+ #include "undefdups.h"
39
+ #include <p4/clientapi.h>
40
+ #include <p4/i18napi.h>
41
+ #include <p4/enviro.h>
42
+ #include <p4/hostenv.h>
43
+ #include <p4/spec.h>
44
+ #include <p4/ignore.h>
45
+ #include <p4/debug.h>
46
+ #include "p4result.h"
47
+ #include "p4rubydebug.h"
48
+ #include "clientuserruby.h"
49
+ #include "specmgr.h"
50
+ #include "p4clientapi.h"
51
+ #include "p4utils.h"
52
+
53
+
54
+
55
+ /*******************************************************************************
56
+ * Our Ruby classes.
57
+ ******************************************************************************/
58
+ extern VALUE cP4; // Base P4 class
59
+ extern VALUE eP4; // Exception class
60
+
61
+
62
+ P4ClientApi::P4ClientApi() : ui( &specMgr )
63
+ {
64
+ debug = 0;
65
+ server2 = 0;
66
+ depth = 0;
67
+ exceptionLevel = 2;
68
+ maxResults = 0;
69
+ maxScanRows = 0;
70
+ maxLockTime = 0;
71
+ InitFlags();
72
+ apiLevel = atoi( P4Tag::l_client );
73
+ enviro = new Enviro;
74
+ prog = "unnamed p4ruby script";
75
+
76
+ client.SetProtocol( "specstring", "" );
77
+
78
+ //
79
+ // Load any P4CONFIG file
80
+ //
81
+ HostEnv henv;
82
+ StrBuf cwd;
83
+
84
+ henv.GetCwd( cwd, enviro );
85
+ if( cwd.Length() )
86
+ enviro->Config( cwd );
87
+
88
+ //
89
+ // Load the current ticket file. Start with the default, and then
90
+ // override it if P4TICKETS is set.
91
+ //
92
+ const char *t;
93
+
94
+ henv.GetTicketFile( ticketFile );
95
+
96
+ if( (t = enviro->Get("P4TICKETS")) )
97
+ ticketFile = t;
98
+
99
+ //
100
+ // Load the current P4CHARSET if set.
101
+ //
102
+ if( client.GetCharset().Length() )
103
+ SetCharset( client.GetCharset().Text() );
104
+ }
105
+
106
+ P4ClientApi::~P4ClientApi()
107
+ {
108
+ if ( IsConnected() )
109
+ {
110
+ Error e;
111
+ client.Final( &e );
112
+ // Ignore errors
113
+ }
114
+ delete enviro;
115
+ }
116
+
117
+ const char *
118
+ P4ClientApi::GetEnv( const char *v)
119
+ {
120
+ return enviro->Get( v );
121
+ }
122
+
123
+ void
124
+ P4ClientApi::SetEnviroFile( const char *c )
125
+ {
126
+ enviro->SetEnviroFile(c);
127
+ }
128
+
129
+ const StrPtr *
130
+ P4ClientApi::GetEnviroFile()
131
+ {
132
+ return enviro->GetEnviroFile();
133
+ }
134
+
135
+ void
136
+ P4ClientApi::SetApiLevel( int level )
137
+ {
138
+ StrBuf b;
139
+ b << level;
140
+ apiLevel = level;
141
+ client.SetProtocol( "api", b.Text() );
142
+ ui.SetApiLevel( level );
143
+ }
144
+
145
+ int
146
+ P4ClientApi::SetCharset( const char *c )
147
+ {
148
+ if( P4RDB_COMMANDS )
149
+ fprintf( stderr, "[P4] Setting charset: %s\n", c );
150
+
151
+ if( c )
152
+ {
153
+ CharSetApi::CharSet cs = CharSetApi::Lookup( c );
154
+ if( cs < 0 )
155
+ {
156
+ StrBuf m;
157
+ m = "Unknown or unsupported charset: ";
158
+ m.Append( c );
159
+ Except( "P4#charset=", m.Text() );
160
+ }
161
+ #ifdef HAVE_RUBY_ENCODING_H
162
+ CharSetApi::CharSet utf8 = CharSetApi::Lookup( "utf8" );
163
+ client.SetTrans( utf8, cs, utf8, utf8 );
164
+ #else
165
+ client.SetTrans( cs, cs, cs, cs );
166
+ #endif
167
+ client.SetCharset( c );
168
+ P4Utils::SetCharset( c );
169
+ }
170
+ else
171
+ {
172
+ // Disables automatic unicode detection if called
173
+ // prior to init (2014.2)
174
+ client.SetTrans( 0 );
175
+ }
176
+ return 1;
177
+ }
178
+
179
+ void
180
+ P4ClientApi::SetCwd( const char *c )
181
+ {
182
+ client.SetCwd( c );
183
+ enviro->Config( StrRef( c ) );
184
+ }
185
+
186
+ void
187
+ P4ClientApi::SetTicketFile( const char *p )
188
+ {
189
+ client.SetTicketFile( p );
190
+ ticketFile = p;
191
+ }
192
+
193
+ void
194
+ P4ClientApi::SetDebug( int d )
195
+ {
196
+ debug = d;
197
+ ui.SetDebug( d );
198
+ specMgr.SetDebug( d );
199
+
200
+ if( P4RDB_RPC )
201
+ p4debug.SetLevel( "rpc=5" );
202
+ else
203
+ p4debug.SetLevel( "rpc=0" );
204
+ }
205
+
206
+ void
207
+ P4ClientApi::SetProtocol( const char *var, const char *val )
208
+ {
209
+ client.SetProtocol( var, val );
210
+ }
211
+
212
+ VALUE
213
+ P4ClientApi::SetEnv( const char *var, const char *val )
214
+ {
215
+ Error e;
216
+
217
+ enviro->Set( var, val, &e );
218
+ if( e.Test() && exceptionLevel )
219
+ {
220
+ Except( "P4#set_env", &e );
221
+ }
222
+
223
+ if( e.Test() )
224
+ return Qfalse;
225
+
226
+ // Fixes an issue on OS X where the next enviro->Get
227
+ enviro->Reload();
228
+
229
+ return Qtrue;
230
+ }
231
+
232
+ //
233
+ // connect to the Perforce server.
234
+ //
235
+
236
+ VALUE
237
+ P4ClientApi::Connect()
238
+ {
239
+ if ( P4RDB_COMMANDS )
240
+ fprintf( stderr, "[P4] Connecting to Perforce\n" );
241
+
242
+ if ( IsConnected() )
243
+ {
244
+ rb_warn( "P4#connect - Perforce client already connected!" );
245
+ return Qtrue;
246
+ }
247
+
248
+ return ConnectOrReconnect();
249
+ }
250
+
251
+ VALUE
252
+ P4ClientApi::ConnectOrReconnect()
253
+ {
254
+ if ( IsTrackMode() )
255
+ client.SetProtocol( "track", "" );
256
+
257
+ Error e;
258
+
259
+ ResetFlags();
260
+ client.Init( &e );
261
+ if ( e.Test() && exceptionLevel )
262
+ Except( "P4#connect", &e );
263
+
264
+ if ( e.Test() )
265
+ return Qfalse;
266
+
267
+ // If a handler is defined, reset the break functionality
268
+ // for the KeepAlive function
269
+
270
+ if( ui.GetHandler() != Qnil )
271
+ {
272
+ client.SetBreak( &ui );
273
+ }
274
+
275
+ SetConnected();
276
+ return Qtrue;
277
+ }
278
+
279
+
280
+ //
281
+ // Disconnect session
282
+ //
283
+ VALUE
284
+ P4ClientApi::Disconnect()
285
+ {
286
+ if ( P4RDB_COMMANDS )
287
+ fprintf( stderr, "[P4] Disconnect\n" );
288
+
289
+ if ( !IsConnected() )
290
+ {
291
+ rb_warn( "P4#disconnect - not connected" );
292
+ return Qtrue;
293
+ }
294
+ Error e;
295
+ client.Final( &e );
296
+ ResetFlags();
297
+
298
+ // Clear the specdef cache.
299
+ specMgr.Reset();
300
+
301
+ return Qtrue;
302
+ }
303
+
304
+ //
305
+ // Test whether or not connected
306
+ //
307
+ VALUE
308
+ P4ClientApi::Connected()
309
+ {
310
+ if( IsConnected() && !client.Dropped() )
311
+ return Qtrue;
312
+ else if( IsConnected() )
313
+ Disconnect();
314
+ return Qfalse;
315
+ }
316
+
317
+ void
318
+ P4ClientApi::Tagged( int enable )
319
+ {
320
+ if( enable )
321
+ SetTag();
322
+ else
323
+ ClearTag();
324
+ }
325
+
326
+ int P4ClientApi::SetTrack( int enable )
327
+ {
328
+ if ( IsConnected() ) {
329
+ if( exceptionLevel )
330
+ {
331
+ Except( "P4#track=", "Can't change performance tracking once you've connected.");
332
+ }
333
+ return Qfalse;
334
+ }
335
+ else if ( enable ) {
336
+ SetTrackMode();
337
+ ui.SetTrack(true);
338
+ }
339
+ else {
340
+ ClearTrackMode();
341
+ ui.SetTrack(false);
342
+ }
343
+ return Qtrue;
344
+ }
345
+
346
+ void P4ClientApi::SetStreams( int enable )
347
+ {
348
+ if ( enable )
349
+ SetStreamsMode();
350
+ else
351
+ ClearStreamsMode();
352
+ }
353
+
354
+ int
355
+ P4ClientApi::GetServerLevel()
356
+ {
357
+ if( !IsConnected() )
358
+ Except( "server_level", "Not connected to a Perforce Server.");
359
+ if( !IsCmdRun() )
360
+ Run( "info", 0, 0 );
361
+ return server2;
362
+ }
363
+
364
+ int
365
+ P4ClientApi::ServerCaseSensitive()
366
+ {
367
+ if( !IsConnected() )
368
+ Except( "server_case_sensitive?", "Not connected to a Perforce Server.");
369
+ if( !IsCmdRun() )
370
+ Run( "info", 0, 0);
371
+ return !IsCaseFold();
372
+ }
373
+
374
+ int
375
+ P4ClientApi::ServerUnicode()
376
+ {
377
+ if( !IsConnected() )
378
+ Except( "server_unicode?", "Not connected to a Perforce Server.");
379
+ if( !IsCmdRun() )
380
+ Run( "info", 0, 0);
381
+ return IsUnicode();
382
+ }
383
+
384
+
385
+ // Check if the supplied path falls within the view of the ignore file
386
+ int
387
+ P4ClientApi::IsIgnored( const char *path )
388
+ {
389
+ Ignore *ignore = client.GetIgnore();
390
+ if( !ignore ) return 0;
391
+
392
+ StrRef p( path );
393
+ return ignore->Reject( p, client.GetIgnoreFile() );
394
+ }
395
+
396
+ //
397
+ // Run returns the results of the command. If the client has not been
398
+ // connected, then an exception is raised but errors from Perforce
399
+ // commands are returned via the Errors() and ErrorCount() interfaces
400
+ // and not via exceptions because one failure in a command applied to many
401
+ // files would interrupt processing of all the other files if an exception
402
+ // is raised.
403
+ //
404
+
405
+ VALUE
406
+ P4ClientApi::Run( const char *cmd, int argc, char * const *argv )
407
+ {
408
+ // Save the entire command string for our error messages. Makes it
409
+ // easy to see where a script has gone wrong.
410
+ StrBuf cmdString;
411
+ cmdString << "\"p4 " << cmd;
412
+ for( int i = 0; i < argc; i++ )
413
+ cmdString << " " << argv[ i ];
414
+ cmdString << "\"";
415
+
416
+ if ( P4RDB_COMMANDS )
417
+ fprintf( stderr, "[P4] Executing %s\n", cmdString.Text() );
418
+
419
+ if ( depth )
420
+ {
421
+ rb_warn( "Can't execute nested Perforce commands." );
422
+ return Qfalse;
423
+ }
424
+
425
+ // Clear out any results from the previous command
426
+ ui.Reset();
427
+
428
+ if ( !IsConnected() && exceptionLevel )
429
+ Except( "P4#run", "not connected." );
430
+
431
+ if ( !IsConnected() )
432
+ return Qfalse;
433
+
434
+ // Tell the UI which command we're running.
435
+ ui.SetCommand( cmd );
436
+
437
+ depth++;
438
+ RunCmd( cmd, &ui, argc, argv );
439
+ depth--;
440
+
441
+ if( ui.GetHandler() != Qnil) {
442
+ if( client.Dropped() && ! ui.IsAlive() ) {
443
+ Disconnect();
444
+ ConnectOrReconnect();
445
+ }
446
+ }
447
+
448
+ ui.RaiseRubyException();
449
+
450
+ P4Result &results = ui.GetResults();
451
+
452
+ if ( results.ErrorCount() && exceptionLevel )
453
+ Except( "P4#run", "Errors during command execution", cmdString.Text() );
454
+
455
+ if ( results.WarningCount() && exceptionLevel > 1 )
456
+ Except( "P4#run", "Warnings during command execution",cmdString.Text());
457
+
458
+ return results.GetOutput();
459
+ }
460
+
461
+
462
+ void
463
+ P4ClientApi::RunCmd( const char *cmd, ClientUser *ui, int argc, char * const *argv )
464
+ {
465
+ client.SetProg( &prog );
466
+ if( version.Length() )
467
+ client.SetVersion( &version );
468
+
469
+ if( IsTag() )
470
+ client.SetVar( "tag" );
471
+
472
+ if ( IsStreams() && apiLevel > 69 )
473
+ client.SetVar( "enableStreams", "" );
474
+
475
+ // If maxresults or maxscanrows is set, enforce them now
476
+ if( maxResults ) client.SetVar( "maxResults", maxResults );
477
+ if( maxScanRows ) client.SetVar( "maxScanRows", maxScanRows );
478
+ if( maxLockTime ) client.SetVar( "maxLockTime", maxLockTime );
479
+
480
+ // If progress is set, set progress var.
481
+ if( ( (ClientUserRuby*)ui)->GetProgress() != Qnil ) client.SetVar( P4Tag::v_progress, 1 );
482
+
483
+ client.SetArgv( argc, argv );
484
+ client.Run( cmd, ui );
485
+
486
+ // Can only read the protocol block *after* a command has been run.
487
+ // Do this once only.
488
+ if( !IsCmdRun() )
489
+ {
490
+ StrPtr *s = 0;
491
+ if ( (s = client.GetProtocol(P4Tag::v_server2)) )
492
+ server2 = s->Atoi();
493
+
494
+ if( (s = client.GetProtocol(P4Tag::v_unicode)) )
495
+ if( s->Atoi() )
496
+ SetUnicode();
497
+
498
+ if( (s = client.GetProtocol(P4Tag::v_nocase)) )
499
+ SetCaseFold();
500
+ }
501
+ SetCmdRun();
502
+ }
503
+
504
+
505
+ //
506
+ // Parses a string supplied by the user into a hash. To do this we need
507
+ // the specstring from the server. We try to cache those as we see them,
508
+ // but the user may not have executed any commands to allow us to cache
509
+ // them so we may have to fetch the spec first.
510
+ //
511
+
512
+ VALUE
513
+ P4ClientApi::ParseSpec( const char * type, const char *form )
514
+ {
515
+ if ( !specMgr.HaveSpecDef( type ) )
516
+ {
517
+ if( exceptionLevel )
518
+ {
519
+ StrBuf m;
520
+ m = "No spec definition for ";
521
+ m.Append( type );
522
+ m.Append( " objects." );
523
+ Except( "P4#parse_spec", m.Text() );
524
+ }
525
+ else
526
+ {
527
+ return Qfalse;
528
+ }
529
+ }
530
+
531
+ // Got a specdef so now we can attempt to parse it.
532
+ Error e;
533
+ VALUE v;
534
+ v = specMgr.StringToSpec( type, form, &e );
535
+
536
+ if ( e.Test() )
537
+ {
538
+ if( exceptionLevel )
539
+ Except( "P4#parse_spec", &e );
540
+ else
541
+ return Qfalse;
542
+ }
543
+
544
+ return v;
545
+ }
546
+
547
+
548
+ //
549
+ // Converts a hash supplied by the user into a string using the specstring
550
+ // from the server. We may have to fetch the specstring first.
551
+ //
552
+
553
+ VALUE
554
+ P4ClientApi::FormatSpec( const char * type, VALUE hash )
555
+ {
556
+ if ( !specMgr.HaveSpecDef( type ) )
557
+ {
558
+ if( exceptionLevel )
559
+ {
560
+ StrBuf m;
561
+ m = "No spec definition for ";
562
+ m.Append( type );
563
+ m.Append( " objects." );
564
+ Except( "P4#format_spec", m.Text() );
565
+ }
566
+ else
567
+ {
568
+ return Qfalse;
569
+ }
570
+ }
571
+
572
+ // Got a specdef so now we can attempt to convert.
573
+ StrBuf buf;
574
+ Error e;
575
+
576
+ specMgr.SpecToString( type, hash, buf, &e );
577
+ if( !e.Test() )
578
+ return P4Utils::ruby_string( buf.Text() );
579
+
580
+ if( exceptionLevel )
581
+ {
582
+ StrBuf m;
583
+ m = "Error converting hash to a string.";
584
+ if( e.Test() ) e.Fmt( m, EF_PLAIN );
585
+ Except( "P4#format_spec", m.Text() );
586
+ }
587
+ return Qnil;
588
+ }
589
+
590
+ //
591
+ // Returns a hash whose keys contain the names of the fields in a spec of the
592
+ // specified type. Not yet exposed to Ruby clients, but may be in future.
593
+ //
594
+ VALUE
595
+ P4ClientApi::SpecFields( const char * type )
596
+ {
597
+ if ( !specMgr.HaveSpecDef( type ) )
598
+ {
599
+ if( exceptionLevel )
600
+ {
601
+ StrBuf m;
602
+ m = "No spec definition for ";
603
+ m.Append( type );
604
+ m.Append( " objects." );
605
+ Except( "P4#spec_fields", m.Text() );
606
+ }
607
+ else
608
+ {
609
+ return Qfalse;
610
+ }
611
+ }
612
+
613
+ return specMgr.SpecFields( type );
614
+ }
615
+
616
+ //
617
+ // Raises an exception or returns Qfalse on bad input
618
+ //
619
+
620
+ VALUE
621
+ P4ClientApi::SetInput( VALUE input )
622
+ {
623
+ if ( P4RDB_COMMANDS )
624
+ fprintf( stderr, "[P4] Received input for next command\n" );
625
+
626
+ if ( ! ui.SetInput( input ) )
627
+ {
628
+ if ( exceptionLevel )
629
+ Except( "P4#input", "Error parsing supplied data." );
630
+ else
631
+ return Qfalse;
632
+ }
633
+ return Qtrue;
634
+ }
635
+
636
+ //
637
+ // Sets the handler and connects the SetBreak feature
638
+ //
639
+ VALUE
640
+ P4ClientApi::SetHandler( VALUE handler )
641
+ {
642
+ if ( P4RDB_COMMANDS )
643
+ fprintf( stderr, "[P4] Received handler object\n" );
644
+
645
+ ui.SetHandler( handler );
646
+
647
+ if( handler == Qnil)
648
+ client.SetBreak(NULL);
649
+ else
650
+ client.SetBreak(&ui);
651
+
652
+ return Qtrue;
653
+ }
654
+
655
+ VALUE
656
+ P4ClientApi::SetProgress( VALUE progress ) {
657
+ if ( P4RDB_COMMANDS )
658
+ fprintf( stderr, "[P4] Received progress object\n" );
659
+
660
+ return ui.SetProgress( progress );
661
+ }
662
+
663
+
664
+ void
665
+ P4ClientApi::GCMark()
666
+ {
667
+ if ( P4RDB_GC )
668
+ fprintf( stderr, "[P4] Ruby asked us to do garbage collection\n" );
669
+
670
+ // We don't hold Ruby objects. But our UI does.
671
+ ui.GCMark();
672
+ }
673
+
674
+ void
675
+ P4ClientApi::Except( const char *func, const char *msg )
676
+ {
677
+ StrBuf m;
678
+ StrBuf errors;
679
+ StrBuf warnings;
680
+ int terminate = 0;
681
+
682
+ m << "[" << func << "] " << msg;
683
+
684
+ // Now append any errors and warnings to the text
685
+ ui.GetResults().FmtErrors( errors );
686
+ ui.GetResults().FmtWarnings( warnings );
687
+
688
+ if( errors.Length() )
689
+ {
690
+ m << "\n" << errors;
691
+ terminate++;
692
+ }
693
+
694
+ if( exceptionLevel > 1 && warnings.Length() )
695
+ {
696
+ m << "\n" << warnings;
697
+ terminate++;
698
+ }
699
+
700
+ if( terminate )
701
+ m << "\n\n";
702
+
703
+ rb_raise( eP4, "%s", m.Text() );
704
+ }
705
+
706
+ void
707
+ P4ClientApi::Except( const char *func, const char *msg, const char *cmd )
708
+ {
709
+ StrBuf m;
710
+
711
+ m << msg;
712
+ m << "( " << cmd << " )";
713
+ Except( func, m.Text() );
714
+ }
715
+
716
+ void
717
+ P4ClientApi::Except( const char *func, Error *e )
718
+ {
719
+ StrBuf m;
720
+
721
+ e->Fmt( &m );
722
+ Except( func, m.Text() );
723
+ }
724
+