clientmain.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014_2/
  • client/
  • clientmain.cc
  • View
  • Commits
  • Open Download .zip Download (23 KB)
/*
 * Copyright 1995, 2000 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

/*
 * clientmain() - Perforce client program
 *
 * See below for usage.
 */

# include <stdhdrs.h>

# include <debug.h>
# include <strbuf.h>
# include <strdict.h>
# include <strtable.h>
# include <strops.h>
# include <error.h>
# include <errornum.h>
# include <options.h>
# include <handler.h>
# include <rpc.h>

# include <pathsys.h>
# include <filesys.h>

# include <ident.h>
# include <ticket.h>
# include <enviro.h>
# include <i18napi.h>
# include <charcvt.h>
# include <charset.h>
# include <diff.h>
# include <diffmerge.h>
# include <hostenv.h>
# include <errorlog.h>
# include <p4tags.h>
# include <md5.h>

# include <msgclient.h>
# include <msgsupp.h>

# include "client.h"
# include "clientmerge.h"
# include "clientuser.h"
# include "clientuserdbg.h"
# include "clientusermsh.h"

/* Ident strings */

Ident ident = { 
	IdentMagic "P4" "/" ID_OS "/" ID_REL "/" ID_PATCH,
	 ID_Y "/" ID_M "/" ID_D 
};

/* Usage info */

ErrorId usage = { ErrorOf( 0, 0, E_FAILED, 0, 0 ), "p4 -h for usage." };

static const char long_usage[] =
"Usage:\n"
"\n"
"    p4 [ options ] command [ arg ... ]\n"
"\n"
"    Options:\n"
"	-b batchsize	specify a batchsize to use with -x (default 128)\n"
"	-h -?		print this message\n"
"	-s		prepend message type to each line of server output\n"
"	-v level	debug modes\n"
"	-V		print client version\n"
"	-x file		read named files as xargs\n"
"	-G		format input/output as marshalled Python objects\n"
"	-z tag		format output as 'tagged' (like fstat)\n"
"	-I		show progress indicators\n"
"\n"
"	-c client	set client name (default $P4CLIENT)\n"
"	-C charset	set character set (default $P4CHARSET)\n"
"	-d dir		set current directory for relative paths\n"
"	-H host		set host name (default $P4HOST)\n"
"	-L language	set message language (default $P4LANGUAGE)\n"
"	-p port		set server port (default $P4PORT)\n"
"	-P password	set user's password (default $P4PASSWD)\n"
"	-q 		suppress all info messages\n"
"	-r retries	Retry command if network times out\n"
"	-Q charset	set command character set (default $P4COMMANDCHARSET)\n"
"	-u user		set user's username (default $P4USER)\n"
"\n"
"    The Perforce client 'p4' requires a valid Perforce server network\n"
"    address 'P4PORT' for most operations, including its help system.\n"
"    Without an explicit P4PORT, the Perforce client will use a default\n"
"    P4PORT of 'perforce:1666'.  That is to say, the host is named 'perforce'\n"
"    and the port number is '1666'.\n"
"\n"
"    The Perforce client accepts configuration via command-line options,\n"
"    P4CONFIG files, environmental variables and on Windows, the registry.\n"
"    Run 'p4 set' to list the client's current settings.\n"
"\n"
"    Run 'p4 help' to ask the Perforce server for information on commands.\n"
"\n"
"    For administrators, see the Perforce server's help output in 'p4d -h'.\n"
"\n"
"    For further information, visit the documentation at www.perforce.com.\n"
"\n";

static void
BadCharset()
{
	printf( "Character set must be one of:\n"
        "none, auto, utf8, utf8-bom, iso8859-1, shiftjis, eucjp, iso8859-15,\n"
        "iso8859-5, macosroman, winansi, koi8-r, cp949, cp1251,\n"
        "utf16, utf16-nobom, utf16le, utf16le-bom, utf16be,\n"
        "utf16be-bom, utf32, utf32-nobom, utf32le, utf32le-bom, utf32be,\n"
	"cp936, cp950, cp850, cp858, cp1253, iso8859-7 or utf32be-bom\n"
        "Check P4CHARSET and your '-C' option.\n" );
}

/* Some forwards */

int clientMain( int argc, char **argv, int &uidebug, Error *e );
int clientMerger( int argc, char **argv, Error *e );
static int clientSet( int argc, char **argv, Options &, Error *e );
static int clientTickets( int argc, char **argv, Options &, Error *e );
int clientReplicate( int argc, char **argv, Options & );
int jtail( int, char **, Options &, Options &, const char * );
extern int commandChaining;

static char argv0[ 1024 ];

/* And now main... */

int
main( int argc, char **argv )
{
	/* Error reporting handle */

	Error e;

	AssertLog.SetTag( "Perforce client" );

	strncpy( argv0, argv[0], sizeof( argv0 ) - 1 );

	int uidebug = 0;
	int ret = clientMain( --argc, ++argv, uidebug, &e );

	AssertLog.Report( &e );

	if( uidebug )
	    printf( "exit: %d\n", ret );

# ifdef OS_VMS
	exit( ret );
# endif

	return ret;
}

void
setVarsAndArgs( Client &client, int argc, char **argv, Options &opts)
{
	StrPtr *s;

	// Set any special -z var=value's, for debug
	// Use -zfunc=something to override default func

	for( int i = 0; s = opts.GetValue( 'z', i ); i++ )
	    client.SetVarV( s->Text() );

	// Set the client program name
	client.SetVar( P4Tag::v_prog, "p4" );
	
	// Set the version used in server log and monitor output
	StrBuf v;
	v << ID_REL "/" << ID_OS << "/" << ID_PATCH;
	client.SetVar( P4Tag::v_version, v.Text() );

	client.SetArgv( argc - 1, argv + 1 );
}

static int
ErrorIncludesRPCSubsystem( Error *e )
{
	for( int eno = e->GetErrorCount(); eno > 0; eno-- )
	{
	    if( e->GetId( eno - 1 )->Subsystem() == ES_RPC )
	        return 1;
	}
	return 0;
}

int
clientMain( int argc, char **argv, int &uidebug, Error *e )
{
	int result;
	int restarted = 0;

	/* Arg processing */

	StrPtr *s;

	// Parse up options

	Options opts;
	int longOpts[] = { Options::Client, Options::Batchsize, Options::User,
	                   Options::Host, Options::Charset,
	                   Options::Help, Options::Port, Options::Password,
	                   Options::CmdCharset, Options::Retries,
	                   Options::Quiet, Options::Progress,
	                   Options::Variable, Options::Xargs, 0 };
	opts.ParseLong( argc, argv,
	        "?b:c:C:d:eE:F:GRhH:M:p:P:l:L:qQ:r#sIu:v:Vx:z:Z:", 
		longOpts, OPT_ANY, usage, e );

	if( e->Test() )
	    return 1;

	// Blast out message.

	if( opts[ 'h' ] || opts[ '?' ] )
	{
	    printf( "%s", long_usage );
	    return 0;
	}

	if( opts[ 'V' ] )
	{
	    StrBuf s;
	    ident.GetMessage( &s );
	    printf( "%s", s.Text() );
	    return 0;
	}

	// Debugging 

	for( int i = 0; s = opts.GetValue( 'v', i ); i++ )
	    p4debug.SetLevel( s->Text() );

	if( opts[ 's' ] || opts[ 'e' ] )
	    ++uidebug;

	// If we recognize the command (merge3, set), do it now */

	if( argc && !strcmp( argv[0], "merge3" ) )
	{
	    return clientMerger( argc - 1, argv + 1, e );
	}
	else if( argc && !strcmp( argv[0], "set" ) )
	{
	    return clientSet( argc - 1, argv + 1, opts, e );
	}
	else if( argc && !strcmp( argv[0], "tickets" ) )
	{
	    return clientTickets( argc - 1, argv + 1, opts, e );
	}
	else if( argc && !strcmp( argv[0], "replicate" ) )
	{
	    return clientReplicate( argc - 1, argv + 1, opts );
	}

	// Misc -E environment updates

	StrBufDict envList;
	StrRef var, val;
	for ( int i = 0 ; s = opts.GetValue( 'E', i ) ; i++ )
	    envList.SetVarV( s->Text() );

	// Apply these updates before and after each Config().
	// Before to make sure we read the right P4CONFIG, and after
	// to make sure that -E vars override P4CONFIG vars.

	// Create client rpc handle.

	Client client;
	for ( int i = 0 ; envList.GetVar( i, var, val ) ; i++ )
	    client.GetEnviro()->Update( var.Text(), val.Text() );

	// Locale setting

	// Early setting of cwd to find right P4CONFIG file...
	if( s = opts[ 'd' ] ) client.SetCwd( s );
	for ( int i = 0 ; envList.GetVar( i, var, val ) ; i++ )
	    client.GetEnviro()->Update( var.Text(), val.Text() );

	Enviro enviro;
	for ( int i = 0 ; envList.GetVar( i, var, val ) ; i++ )
	    enviro.Update( var.Text(), val.Text() );

	const char *lc;

	enviro.Config( client.GetCwd() );
	for ( int i = 0 ; envList.GetVar( i, var, val ) ; i++ )
	    enviro.Update( var.Text(), val.Text() );

	if( ( s = opts[ 'l' ] ) || ( s = opts[ 'C' ] ) )
	{
	    lc = s->Text();
	}
	else
	{
	    lc = enviro.Get( "P4CHARSET" );
	}

	if( lc )
	{
	    CharSetCvt::CharSet cs = CharSetCvt::Lookup( lc );

	    if( (int)cs == -1 )
	    {
		// bad charset specification
		BadCharset();
		return 1;
	    }
	    else
	    {
		CharSetCvt::CharSet ws = cs;

		if( ( s = opts[ 'Q' ] ) ||
		    ( lc = enviro.Get( "P4COMMANDCHARSET" ) ) )
		{
		    if( s )
			lc = s->Text();
		    cs = CharSetCvt::Lookup( lc );
		    if( (int)cs == -1 )
		    {
			printf( "P4COMMANDCHARSET unknown\n" );
			return 1;
		    }
		}
		if( CharSetApi::Granularity( cs ) != 1 )
		{
		    printf( "p4 can not support a wide charset unless\n"
			    "P4COMMANDCHARSET is set to another charset.\n" );
		    return 1;
		}
		client.SetTrans( cs, ws, cs, cs );

		for ( int i = 0 ; envList.GetVar( i, var, val ) ; i++ )
		     client.GetEnviro()->Update( var.Text(), val.Text() );
	    }
	}

	// Get command line overrides of user, client, cwd, port
	
	if( s = opts[ 'c' ] ) client.SetClient( s );
	if( s = opts[ 'H' ] ) client.SetHost( s );
	if( s = opts[ 'L' ] ) client.SetLanguage( s );
	if( s = opts[ 'd' ] ) client.SetCwd( s );
	if( s = opts[ 'u' ] ) client.SetUser( s );
	if( s = opts[ 'p' ] ) client.SetPort( s );
	if( s = opts[ 'P' ] )
	{
	    client.SetPassword( s );
	    memset( s->Text(), '\0', s->Length() );
	}
	client.SetExecutable( argv0 );

	// Specify a batch size

	int batchSize = opts[ 'b' ] ? opts[ 'b' ]->Atoi() : 128;
	if( batchSize < 1 ) 
	    batchSize = 1;

	// Any protocol settings?

	for( int i = 0; s = opts.GetValue( 'Z', i ); i++ )
	    client.SetProtocolV( s->Text() );

	// -G or -R implies -Z tag -Z sendspec

	if( opts[ 'G' ] || opts[ 'R' ] )
	{
	    client.SetProtocol( P4Tag::v_tag );
	    client.SetProtocol( P4Tag::v_specstring );
	}

	// -M implies -Z sendspec
	if( opts[ 'M' ] )
	    client.SetProtocol( P4Tag::v_specstring );

	// Set default api value high,  client floats with server output
	// This high value also lets the server know that the client can 
	// handle streams (i.e. don't change this).
	// Ignore above,  unfortunately there are other api's using the
	// magic 99999,  although there is server support to prevent this
	// we shall enable streams here for future gratification.

	client.SetProtocol( P4Tag::v_api, "99999" );
	client.SetProtocol( P4Tag::v_enableStreams );

    restart:

	// Connect.

	client.Init( e );

	if( e->Test() )
	    return 1;

	// Normal client -- will connect to server.
	// -s means use debug display mode.

	ClientUser *ui;

	if( opts[ 's' ] )
	    ui = new ClientUserDebug;
	else if( opts[ 'e' ] )
	    ui = new ClientUserDebugMsg;
	else if( opts[ 'F' ] )
	    ui = new ClientUserFmt( opts[ 'F' ] );
	else if( opts[ 'G' ] )
	    ui = new ClientUserPython;
	else if( opts[ 'R' ] )
	    ui = new ClientUserRuby;
	else if( ( s = opts[ 'M' ] ) && ( *s == "g" ) )
	    ui = new ClientUserPython;
	else if( ( s = opts[ 'M' ] ) && ( *s == "r" ) )
	    ui = new ClientUserRuby;
	else if( ( s = opts[ 'M' ] ) && ( *s == "p" ) )
	    ui = new ClientUserPhp;
	else if( opts[ 'I' ] )
	    ui = new ClientUserProgress;
	else
	    ui = new ClientUser;

	if( opts[ 'q' ] )
	    ui->SetQuiet();

	// Invoke operation in server:
	//	if not -x, bundle up argv and send it on down.
	//	if -x, bundle up argv and a few lines from the file.

	StrPtr *xargsName;

	if( !( xargsName = opts[ 'x' ] ) )
	{
	    // Normal invocation.

	    setVarsAndArgs( client, argc, argv, opts );
	    client.Run( argv[0], ui );
	}
	else if( argc == 1 && !strcmp( argv[0], "run" ) )
	{
	    // p4 -x cmdfile run: serially run multiple commands, same client
	    commandChaining = 1;
	    StrBuf s;
	    FileSys *xf = FileSys::Create( FST_TEXT );

	    xf->Set( *xargsName );
	    xf->Open( FOM_READ, e );

	    if( e->Test() )
	    {
		delete xf;
		return 1;
	    }

	    while( !e->Test() && !client.Dropped() && !client.GetFatals() )
	    {
		int eof = xf->ReadLine( &s, e );
		if( eof < 0 )
		{
	            e->Set( MsgClient::LineTooLong ) << xf->BufferSize();
		    break;
		}
		else if ( eof > 0 )
		{
	            StrBuf	wordBuffer;
	            char	*words[2048];
		    int 	nwords;
		    
		    nwords = StrOps::Words( wordBuffer, s.Text(), words,
				    ( sizeof( words ) / sizeof( words[0] ) ) );
		    
		    if( !nwords )
	                continue; //blank line in file

	            setVarsAndArgs( client, argc, argv, opts );

	            for( int i = 1; i < nwords; i++ )
		        client.translated->SetVar( "", words[ i ] );

		    client.Run( words[0], ui );
		    fflush( stdout );
		    fflush( stderr );
		}
		else
		{
		    break;
		}
	    }

	    xf->Close( e );
	    delete xf;
	}
	else
	{
	    // -x invocation

	    StrBuf s;
	    FileSys *xf = FileSys::Create( FST_TEXT );
	    int eof = 0;
	    int count = 0;

	    // Open -x file

	    xf->Set( *xargsName );
	    xf->Open( FOM_READ, e );

	    if( e->Test() )
	    {
		delete xf;
		return 1;
	    }

	    // In blocks of batchSize args.

	    while( !e->Test() && !client.Dropped() && !eof )
	    {
		// If more...

		if( !( eof = !xf->ReadLine( &s, e ) ) )
		{
		    // Set user's (constant) args at beginning of block

		    if( !count )
			setVarsAndArgs( client, argc, argv, opts );

		    // Set this arg.

		    client.translated->SetVar( "", &s );
		}

		// Dispatch

		if( eof ? count : ++count == batchSize )
		{
		    client.Run( argv[0], ui );
		    count = 0;
		}
	    }

	    // Seal off xargs file.

	    xf->Close( e );
	    delete xf;
	}

	// Since we only did sync Client::Run() calls, the ui should
	// no longer be necessary.

	delete ui;

	// Close and clean.

	result = client.Final( e );

	if( result && ErrorIncludesRPCSubsystem( e ) &&
	    opts[ 'r' ] && ( restarted++ < opts[ 'r' ]->Atoi() ) )
	{
	    e->Clear();
	    goto restart;
	}

	return result;
}

int
clientMerger( int argc, char **argv, Error *e )
{
	// Parse up options

	Options opts;
	int longOpts[] = { Options::DiffFlags, Options::BinaryAsText,
	                   Options::Verbose, 0 };
	opts.ParseLong( argc, argv, "d:rtvx:", longOpts, OPT_THREE, usage, e );

	if( e->Test() )
	    return 1;

	int rcsstyle = opts[ 'r' ] != 0;
	int showall = opts[ 'v' ] != 0;
        StrPtr *checkMerge = opts[ 'x' ];

	/* Run as 3-way diff for merging */

	FileSys *f1 = FileSys::Create( FST_BINARY );
	FileSys *f2 = FileSys::Create( FST_BINARY );
	FileSys *f3 = FileSys::Create( FST_BINARY );

	f1->Set( argv[0] );
	f2->Set( argv[1] );
	f3->Set( argv[2] );

	StrBuf baseDigest;
	StrBuf l1Digest;
	StrBuf l2Digest;

	MD5 baseMD5;
	MD5 resultMD5;
	MD5 l1MD5;
	MD5 l2MD5;
	
	if( checkMerge )
	{
	    f1->Digest( &baseDigest, e );
	    f2->Digest( &l1Digest, e );
	    f3->Digest( &l2Digest, e );

	    e->Clear();
	}

	DiffFlags flags( opts[ 'd' ] );
	LineType lineType = opts[ 't' ] ? LineTypeRaw : LineTypeLocal;

	DiffMerge *m = new DiffMerge( f1, f2, f3, flags, lineType, e );
	AssertLog.Abort( e );

	StrBuf sBuf;
	char buf[4096];
	int oldbits = 0, bits;
	int len;

	for( ; bits = m->Read( buf, sizeof( buf ), &len ); oldbits = bits )
	{
	    /* Merge check */

	    if( checkMerge )
	    {
	        sBuf.Clear();
	        sBuf << buf;
	        sBuf.SetLength( len );

		if( bits & SEL_BASE )
	        {
	            if( *checkMerge == "base" ) 
		        fwrite( buf, 1, len, stdout );
	            baseMD5.Update( sBuf );
	        }
		if( bits & SEL_LEG1)
	        {
	            if( *checkMerge == "leg1" ) 
		        fwrite( buf, 1, len, stdout );
	            l1MD5.Update( sBuf );
	        }
		if( bits & SEL_LEG2 )
		{
	            if( *checkMerge == "leg2" ) 
		        fwrite( buf, 1, len, stdout );
	            l2MD5.Update( sBuf );
		}
	        if( bits & SEL_RSLT || bits == ( SEL_BASE | SEL_CONF ) )
	        {
	            if( *checkMerge == "result" ) 
		        fwrite( buf, 1, len, stdout );
	           resultMD5.Update( sBuf );
	        }

	        continue; 
	    }

	    /* Display tag */

	    if( !oldbits || oldbits == bits )
	    {
		/* OK */;
	    }
	    else if( !rcsstyle )
	    {
		printf( ">>>> %s\n", m->BitNames( bits ) );
	    }
	    else if( showall || 
		     bits & SEL_CONF || 
		     bits == SEL_ALL && oldbits & SEL_CONF )
	    {
		switch( bits )
		{
		case SEL_ALL:
		    printf( "<<<<\n" );
		    break;

		case SEL_BASE:
		case SEL_BASE|SEL_CONF:
		case SEL_BASE|SEL_LEG1:
		case SEL_BASE|SEL_LEG2:
		    printf( ">>>> %s\n", m->BitNames( bits ) );
		    break;

		case SEL_LEG1|SEL_RSLT|SEL_CONF:
		    /* fall through */

		case SEL_LEG2|SEL_RSLT|SEL_CONF:
		case SEL_LEG1|SEL_RSLT:
		case SEL_LEG2|SEL_RSLT:
		case SEL_LEG1|SEL_LEG2|SEL_RSLT:
		    printf( "==== %s\n", m->BitNames( bits ) );
		    break;
		}
	    }

	    /* Display data */

	    if( !rcsstyle || showall || 
	        bits & SEL_RSLT || 
		bits == ( SEL_BASE | SEL_CONF ) )
	    {
		fwrite( buf, 1, len, stdout );
	    }
	}

	if( checkMerge && *checkMerge == "check" )
	{
	    StrBuf baseFinal;
	    StrBuf l1Final;
	    StrBuf l2Final;
	    StrBuf resultFinal;

	    resultMD5.Final( resultFinal );
	    baseMD5.Final( baseFinal );
	    l1MD5.Final( l1Final );
	    l2MD5.Final( l2Final );

	    if( baseDigest.Compare( baseFinal ) )
	        printf("BAD (base)\n");
	    if( l1Digest.Compare( l1Final ) )
	        printf("BAD (leg1)\n");
	    if( l2Digest.Compare( l2Final ) )
	        printf("BAD (leg2)\n");

	    if( ( !baseDigest.Compare( l1Digest ) &&
	           l2Digest.Compare( resultFinal ) )
	      ||( !baseDigest.Compare( l2Digest ) &&
	           l1Digest.Compare( resultFinal ) ) )
	        printf("BAD (safe-merge)\n");
	}

	delete m;
	delete f1;
	delete f2;
	delete f3;

	return 0;
}

int
clientTickets( int argc, char **argv, Options &global_opts, Error *e )
{
	const char *c;
	HostEnv h;
	StrBuf cwd;
	StrBuf ticketfile;
	StrBuf output;

	// Parse up options

	Options opts;
	int longOpts[] = { Options::Charset, Options::CmdCharset, 0 };
	opts.ParseLong( argc, argv, "C:l:Q:", longOpts, OPT_ANY, usage, e );

	if( e->Test() )
	    return 1;

	if( global_opts[ 'd' ] )
	    cwd.Set( global_opts[ 'd' ] );
	else
	    h.GetCwd( cwd );
	Enviro enviro;
	enviro.Config( cwd );
	const char *lc;
	StrPtr *s;
	CharSetCvt::CharSet cs = CharSetCvt::NOCONV;

	if( ( s = opts[ 'l' ] ) || ( s = opts[ 'C' ] ) )
	{
	    lc = s->Text();
	}
	else
	{
	    lc = enviro.Get( "P4CHARSET" );
	}

	if( lc )
	{
	    cs = CharSetCvt::Lookup( lc );

	    if( (int)cs == -1 )
	    {
		// bad charset specification
		BadCharset();
		return 1;
	    }
	    else
	    {
		if( ( s = opts[ 'Q' ] ) ||
		    ( lc = enviro.Get( "P4COMMANDCHARSET" ) ) )
		{
		    if( s )
			lc = s->Text();
		    cs = CharSetCvt::Lookup( lc );
		    if( (int)cs == -1 )
		    {
			printf( "P4COMMANDCHARSET unknown\n" );
			return 1;
		    }
		}
		if( CharSetApi::Granularity( cs ) != 1 )
		{
		    printf( "p4 can not support a wide charset unless\n"
			    "P4COMMANDCHARSET is set to another charset.\n" );
		    return 1;
		}
		if( cs )
		{
		    GlobalCharSet::Set( cs );
		    enviro.SetCharSet( cs );
	            if( !global_opts[ 'd' ] )
		        h.GetCwd( cwd, &enviro );
		    enviro.Config( cwd );
		}
	    }
	}

	if( c = enviro.Get( "P4TICKETS" ) )
	    ticketfile.Set( c );
	else
	    h.GetTicketFile( ticketfile );

	Ticket t( &ticketfile );
	t.List( output );

	if( cs )
	{
	    // i18n mode - need to translate from utf8 to P4CHARSET

	    CharSetCvt *converter = CharSetCvt::FindCvt( CharSetCvt::UTF_8, cs );
	    if( converter )
	    {
		c = converter->FastCvtQues( output.Text(), output.Length() );
		printf( "%s", c ? c : output.Text() );
		delete converter;
	    }
	}
	else
	    printf( "%s", output.Text() );
	
	return 0;
}

int
clientSet( int argc, char **argv, Options &global_opts, Error *e )
{
	/* p4 set - manipulate env vars */

	StrBuf var;

	// Parse up options

	Options opts;
	int longOpts[] = { Options::System, Options::Service,
	                   Options::Charset, Options::CmdCharset, 0 };
	opts.ParseLong( argc, argv, "sS:C:l:Q:", longOpts, OPT_ANY, usage, e );

	if( e->Test() )
	    return 1;

	// Blast out message.

	Enviro enviro;
	HostEnv h;
	StrBuf cwd;

	if( opts[ 's' ] )
	    enviro.BeServer();

	if( opts[ 'S' ] )
	{
	    int serviceExists = enviro.BeServer( opts[ 'S' ], !argc );
	    if( !serviceExists )
	    {
	        printf( "Perforce service '%s' does not exist.\n",
	                opts[ 'S' ]->Text() );
	        return 1;
	    }
	}
	    
	if( global_opts[ 'd' ] )
	    cwd.Set( global_opts[ 'd' ] );
	else
	    h.GetCwd( cwd, &enviro );

	StrBuf charsetVar;

	const char *lc;
	StrPtr *s;
	enviro.LoadConfig( cwd, argc == 0 );

	if( s = global_opts[ 'p' ] )
	    lc = s->Text();
	else if( ! ( lc = enviro.Get( "P4PORT" ) ) )
	    lc = "perforce:1666";

	charsetVar.Set( "P4_" );
	if( strchr( lc, '=' ) )
	{
	    // If the port contains an equals we replace it
	    // since environment names can not have an equals
	    StrBuf tmp( lc );

	    StrOps::Sub( tmp, '=', '@' );
	    charsetVar.Append( &tmp );
	}
	else
	    charsetVar.Append( lc );
	charsetVar.Append( "_CHARSET" );

	if( ( s = opts[ 'l' ] ) || ( s = opts[ 'C' ] ) )
	{
	    lc = s->Text();
	}
	else
	{
	    lc = enviro.Get( "P4CHARSET" );
	    if( !lc )
		lc = enviro.Get( charsetVar.Text() );
	}

	if( lc )
	{
	    CharSetCvt::CharSet cs = CharSetCvt::Lookup( lc );

	    if( (int)cs == -1 )
	    {
		// bad charset specification
		BadCharset();
		printf(	"Continuing as if not set.\n" );
	    }
	    else
	    {
		if( ( s = opts[ 'Q' ] ) ||
		    ( lc = enviro.Get( "P4COMMANDCHARSET" ) ) )
		{
		    if( s )
			lc = s->Text();
		    cs = CharSetCvt::Lookup( lc );
		    if( (int)cs == -1 )
		    {
			printf( "P4COMMANDCHARSET unknown\n" );
			return 1;
		    }
		}
		if( CharSetApi::Granularity( cs ) != 1 )
		{
		    printf( "p4 can not support a wide charset unless\n"
			    "P4COMMANDCHARSET is set to another charset.\n" );
		    return 1;
		}
		if( cs )
		{
		    GlobalCharSet::Set( cs );
		    enviro.SetCharSet( cs );
	            if( !global_opts[ 'd' ] )
		        h.GetCwd( cwd, &enviro );
		    enviro.Config( cwd );
		}
	    }
	}

	if( !argc )
	{
	    enviro.List();

	    enviro.Print( charsetVar.Text() );

	    int o = 0;
	    while( p4tunable.GetName( o ) )
	    {
	        if( p4tunable.IsSet( o ) )
	            printf( "%s: %d\n",
	                    p4tunable.GetName( o ), p4tunable.Get( o ) );
	        o++;
	    }
	}
	else for( ; argc; argc--, argv++ )
	{
	    char *equals;

	    if( equals = strchr( *argv, '=' ) )
	    {
		var.Set( *argv, equals - *argv );
		++equals;
		if( ( var == "P4CHARSET" || var.EndsWith( "_CHARSET", 8 ) )
		    && *equals )
		{
		    CharSetApi::CharSet cs = CharSetCvt::Lookup( equals );
		    if( (int)cs == -1 )
		    {
			printf("Will not set P4CHARSET to an invalid value.\n");
			continue;
		    }
		    if( CharSetApi::Granularity( cs ) != 1 )
		    {
			lc = enviro.Get( "P4COMMANDCHARSET" );

			if( !lc || 
			    -1 == (int)( cs = CharSetCvt::Lookup( lc ) ) ||
			    CharSetApi::Granularity( cs ) != 1 )
			    printf( "p4 can not support a wide charset unless\n"
			        "P4COMMANDCHARSET is set to another charset.\n" );
			return 1;
		    }
		}
		enviro.Set( var.Text(), equals, e );
		AssertLog.Report( e );
		e->Clear();
	    }
	    else
	    {
		enviro.Print( *argv );
	    }
	}

	return 0;
}

static ErrorId replicateUsage = { ErrorOf( 0, 0, 0, 0, 0),
	"p4 [ -p port ][ -u user ][ -P password ][ -C charset ] replicate\n"
	"\t   [ -j token ][ -s statefile ][ -i interval ][ -k -x -R ]\n"
	"\t   [ -J prefix ][ -T tables excluded ][ -o output ][ command ]\n"
};

int
clientReplicate( int ac, char **av, Options &preops )
{
	Error e;

	AssertLog.SetTag( "replicate" );

	Options opts;

	int longOpts[] = { Options::JournalPrefix, Options::Repeat,
	                   Options::OutputFile, 0 };
	opts.ParseLong( ac, av, "xkj:i:J:s:o:T:R", longOpts,
	                OPT_ANY, replicateUsage, &e );

	if( e.Test() )
	{
	    AssertLog.Report( &e );
	    return 1;
	}

	return jtail( ac, av, preops, opts, "replicate" );
}
# Change User Description Committed
#2 15903 Matt Attaway Everything should be happy now between the Workshop and the depot paths
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.2/client/clientmain.cc
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code