p4ruby 2014.1 → 2014.2.0.pre1

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