clientinit.cc #1

  • //
  • guest/
  • Newtopian/
  • p4/
  • 2015.1/
  • client/
  • clientinit.cc
  • View
  • Commits
  • Open Download .zip Download (36 KB)
/*
 * Copyright 1995, 2000 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

/*
 * clientinit() - Perforce client DVCS init
 *
 */

# define NEED_CHDIR
# define NEED_TYPES
# define NEED_STAT
# define NEED_FLOCK

# include <stdhdrs.h>

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

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

# include <enviro.h>
# include <handler.h>
# include <runcmd.h>

# include <msgclient.h>
# include <p4tags.h>

# include "client.h"
# include "clientuser.h"

# ifdef OS_NT
# define INIT_CONFIG "p4config.txt"
# define INIT_IGNORE "p4ignore.txt"
# else
# define INIT_CONFIG ".p4config"
# define INIT_IGNORE ".p4ignore"
# endif

# define INIT_ROOT ".p4root"
# define INIT_SERVERID ".p4root/server.id"

static ErrorId initClone = { ErrorOf( 0, 0, E_INFO, 0, 0),
"\n"
"	clone -- Clone a new private local Perforce repository from a remote.\n"
"\n"
"	p4 [-u user][-d dir][-c client] clone [-m depth -v] -p port -r remote\n"
"	p4 [-u user][-d dir][-c client] clone [-m depth -v] -p port -f filespec\n"
"\n"
"	This command initializes a new Perforce repository in the current\n"
"	directory (or the directory specified by the -d flag).\n"
"\n"
"	In order to run 'p4 clone', you must have up-to-date and matching\n"
"	versions of the 'p4' and 'p4d' executables in your operating system\n"
"	path. You can download these executables from http://www.perforce.com\n"
"\n"
"	Perforce database files will be stored in the directory named\n"
"	\".p4root\". Perforce configuration settings will be stored in\n"
"	P4CONFIG and P4IGNORE files in the top level of your directory.\n"
"	It is not necessary to view or update these files, but you should\n"
"	be aware that they exist.\n"
"\n"
"	The -m flag performs a shallow fetch; only the last number of\n"
"	specified revisions of each file are fetched.\n"
"\n"
"	The -p flag specifies the address of the target server you wish\n"
"	to clone from.\n"
"\n"
"	The -r flag specifies a remote spec installed on the remote server\n"
"	to use as a template for the clone and stream setup. See 'p4 help\n"
"	remote' for more information about how the clone command interprets\n"
"	the remote spec during the setup steps.\n"
"\n"
"	The -f flag specifies a filepath in the remote server to use as the\n"
"	path to clone; this path will also be used to determine the stream\n"
"	setup in the local server.\n"
"\n"
"	The -v flag specifies verbose mode.\n"
"\n"
"	When the clone completes, a default remote spec will be created,\n"
"	called origin. To update your local repository with the latest\n"
"	changes from the target server, run 'p4 fetch'.\n"
"\n"
"	For more information about using Perforce, run 'p4 help' after you\n"
"	have run 'p4 clone', or visit http://www.perforce.com.\n"
};

static ErrorId initHelp = { ErrorOf( 0, 0, E_INFO, 0, 0),
"\n"
"	init -- Initialize a new private local Perforce repository.\n"
"\n"
"	p4 [-u user][-d dir][-c client] init [-hq][-c stream][-p port]\n"
"	p4 [-u user][-d dir][-c client] init [-hq][-c stream][-Cx -xi -n]\n"
"\n"
"	This command initializes a new Perforce repository in the current\n"
"	directory (or the directory specified by the -d flag).\n"
"\n"
"	In order to run 'p4 init', you must have up-to-date and matching\n"
"	versions of the 'p4' and 'p4d' executables in your operating system\n"
"	path. You can download these executables from http://www.perforce.com\n"
"\n"
"	Perforce database files will be stored in the directory named\n"
"	\".p4root\". Perforce configuration settings will be stored in\n"
"	P4CONFIG and P4IGNORE files in the top level of your directory.\n"
"	It is not necessary to view or update these files, but you should\n"
"	be aware that they exist.\n"
"\n"
"	After initializing your new repository, run 'p4 reconcile' to mark\n"
"	all of your source files to be added to Perforce, then 'p4 submit'\n"
"	to submit them.\n"
"\n"
"	The -c flag configures the installation to create the specified stream\n"
"	as the mainline stream rather than the default '//stream/main'.\n"
"\n"
"	The -p flag specifies the address of a target server you wish to\n"
"	discover the case sensitivity and unicode settings from. This will\n"
"	make your local repository compatible with that server.\n"
"\n"
"	The -q flag suppresses informational messages (including if\n"
"	p4 init has already been run.)\n"
"\n"
"	The -Cx flag sets the case sensitivity of the new Perforce\n"
"	installation. You may specify either -C0 or -C1. The -C0 flag\n"
"	specifies case-sensitive operation; the -C1 flag specifies\n"
"	case-insensitive operation. (see discovery note below).\n"
"\n"
"	The -n flag configures the installation without unicode support.\n"
"	The -xi flag configures the installation with unicode support.\n"
"	Without -n or -xi, the installation will decide on unicode support\n"
"	based on the P4CHARSET enviroment being set.\n"
"\n"
"	Note: If neither -Cx, -xi or -n flags are set, init will try and\n"
"	discover the correct settings from a server already set in your\n"
"	environment.  If init cannot find a server then it will fail\n"
"	initialization.\n"
"\n"
"	For more information about using Perforce, run 'p4 help' after you\n"
"	have run 'p4 init', or visit http://www.perforce.com.\n"
};

static ErrorId initUsage = { ErrorOf( 0, 0, E_FAILED, 0, 0),
	"Usage: p4 [-u user][-d dir][-c client] init [-hq][-c stream][-p port | -Cx -xi -n]\n"
	"\tUse p4 init -h for detailed help."
};

static ErrorId cloneUsage = { ErrorOf( 0, 0, E_FAILED, 0, 0),
	"Usage: p4 [-u user][-d dir][-c client] clone -p <port> [-m depth -v][-r <remote> | -f filespec]\n"
	"\tUse p4 clone -h for detailed help."
};

static ErrorId initUnicodeMixup = { ErrorOf( 0, 0, E_FAILED, 0, 0),
	"p4 init does not allow '-n' and '-xi' to be used together"
};

static ErrorId initCaseFlagMixup = { ErrorOf( 0, 0, E_FAILED, 0, 0),
	"Specify either -C0 or -C1."
};

static ErrorId initServerFail = { ErrorOf( 0, 0, E_FAILED, 0, 0),
	"\np4d server failed to initialize.  A 2015.1 or later p4d server\n"
	"must be in your path and runable."
};

class CloneUser : public ClientUserProgress {

	public:
	        CloneUser(){
	            commandError = 0;
	            needLogin = 0;
	            gotRemote = 0;
	            quiet = 1;
		}

	// implement user callbacks

	void		OutputStat( StrDict *varList ); 
	void		OutputError( const char *errBuf );
	void		OutputText( const char *data, int length );
	void		OutputInfo( char level, const char *data );
	void		InputData( StrBuf *strbuf, Error *e );

	// what command is running ?

	void		SetCommand( const char *p ) { command.Set( p ); }
	StrPtr		&GetCommand() { return command; }

	// fun stuff

	void		GetStreamName( StrBuf *filePath, StrPtr &val );
	int		StreamExists( StrPtr &filePath );
	int		TooWide( const char *s );
	const char	*Trim( StrPtr &filePath, StrPtr &val );
	int		Unicode() { return unicode; }
	int		MaxChange() { return maxCommitChange; }
	int		Security() { return security; }
	int		GotError() { return commandError; }
	void		SetError() { commandError = 1; }
	void		ClearError() { commandError = 0; }
	int		GotRemote(){ return gotRemote; }
	int		NeedLogin(){ return needLogin; }
	StrPtr		CaseFlag() { return caseFlag; }
	StrPtr		Server()   { return serverAddress; }
	StrPtr		Description() { return description; }
	void		SetDescription( const char *d ) 
			              { description.Set( d ); }
	StrPtr		GetUser() { return user; }
	void		SetUser   ( const char *u )
			          { user.Set( u ); }
	StrPtr		UserName()    { return userName; }
	void		DoDebug()  { quiet = 0; }
	StrBuf		*GetData() { return &inputData; }
	StrBufDict	*GetStreams() { return &mainlines; }
	StrBufDict	*Dict() { return &remoteMap; }

	private:

	// p4 info
	int		unicode;
	int		security;
	StrBuf		caseFlag;

	// p4 remote
	StrBufDict	remoteMap;
	StrBuf		depotName;
	StrBuf		description;
	StrBuf		serverAddress;
	StrBuf		inputData;
	StrBuf		userName;
	StrBuf		user;
	int		gotRemote;
	int		needLogin;

	// p4 counter
	int		maxCommitChange;
	
	// streams
	StrBufDict	mainlines;

	// command tracking
	StrBuf		command;
	int		commandError;
	int		quiet;

} ;

void cloneSetup( StrPtr *, StrPtr *, StrPtr *, StrPtr *, CloneUser *, Enviro *, Error * );
void cloneFetch( StrPtr *, StrPtr *, StrPtr *, StrPtr *, StrPtr *, CloneUser *, StrPtr *, Enviro *, Error * );
void initService( StrPtr *, CloneUser *, StrPtr *, Enviro *, Error * );
void initDiscover( StrPtr *, StrPtr *, CloneUser *, Enviro *, Error * );
int  initValidate( StrPtr * );
static void WriteIgnore( StrPtr *, StrPtr *, int, Error * );
static void LockCheck( Error * );

int
clientInitHelp( int doClone, Error *e )
{
	ClientUser cuser;
	e->Set( doClone ? initClone : initHelp );
	cuser.Message( e );
	e->Clear();
	return 0;
}

int
clientInit( int ac, char **av, Options &preops, int doClone, Error *e )
{
	Options opts;
	ClientUser cuser;
	CloneUser ruser;
	int doUnicode = 0;
	int doDiscover = 0;
	StrBuf remote;
# ifndef OS_NT
	int oldumask;
# endif

	int longOpts[] = { Options::Help, Options::Quiet, 0 };

	if( doClone )
	    opts.ParseLong( ac, av, "hf:m#p:r:v", longOpts, OPT_NONE, cloneUsage, e );
	else
	    opts.ParseLong( ac, av, "c:hnp:qC#x:", longOpts, OPT_NONE, initUsage, e );

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


	StrPtr *sp;
	StrRef fileref( "file" );
	Client *client = NULL;
	Enviro *enviro = NULL;
	RunCommand rc;
	RunArgs ra;
	StrBuf clientname;
	StrBuf user;
	FileSys *fsys = NULL;
	FileSys* rootDir = NULL;
	PathSys *curdir = NULL;
	StrBuf config( INIT_CONFIG );
	StrBuf ignore( INIT_IGNORE );
	StrBuf serverID( INIT_SERVERID );
	const char *s;
	int ecode = 0;

	// turn-off quiet mode
	if( preops[ 'v' ] )
	    ruser.DoDebug();

	if( !doClone && opts[ 'h' ] )
	    return clientInitHelp( 0, e );

	if( opts[ 'C' ] )
	{
	    int caseVal = opts[ 'C' ]->Atoi();
	    if( caseVal != 0 && caseVal != 1 )
	    {
	        e->Set( initCaseFlagMixup );
	        goto finish;
	    }
	}
	enviro = new Enviro;

	if( s = enviro->Get( "P4CONFIG" ) )
	    config.Set( s );
	else
	{
	    enviro->Set( "P4CONFIG", config.Text(), e );
	    delete enviro;
	    enviro = new Enviro;
	}

	fsys = FileSys::Create( FST_TEXT );
	rootDir = FileSys::Create( FST_TEXT );

	// Load config/enviro

	// check if already initialized
	// P4INITROOT and .p4root dir

	curdir = PathSys::Create();

	client = new Client( enviro );

	curdir->Set( client->GetCwd() );
	if( ( sp = preops[ 'd' ] ) )
	{
	    curdir->SetLocal( *curdir, *sp );
	    curdir->SetLocal( *curdir, fileref );
	    fsys->MkDir( *curdir, e );
	    if( e->Test() )
	    {
		ecode = 1;
		goto finish;
	    }
	    curdir->ToParent();
	    if( chdir( curdir->Text() ) < 0 )
	    {
		ecode = 1;
		goto finish;
	    }
	    delete client;
	    delete enviro;
	    enviro = new Enviro;
	    enviro->Update( "PWD", curdir->Text() );
	    client = new Client( enviro );
	}

	s = enviro->Get( "P4INITROOT" );
	fsys->Set( INIT_ROOT );

	if( s || ( fsys->Stat() & FSF_EXISTS ) )
	{
	    if( !opts[ 'q' ] )
		printf( "Nothing to do - existing dvcs tree at %s\n", 
	                s ? s : INIT_ROOT );
	    ecode = 1;
	    goto finish;
	}

	// check for locking issues

	LockCheck( e );

	if( e->Test() )
	{
	    // add a user polite error about locking
	    e->Set( MsgClient::LockCheckFail );
	    goto finish;
	}

	if( doClone )
	{
	    if( opts[ 'h' ] )
	        return clientInitHelp( 1, e );

	    if( (  opts[ 'r' ] &&  opts[ 'f' ] ) ||
	        ( !opts[ 'f' ] && !opts[ 'r' ] ) ||
	        ( !opts[ 'p' ] ) )
	    {
	        e->Set( cloneUsage );
	        goto finish;
	    }

	    // Pull down all the clone info & stash it

	    cloneSetup( opts[ 'p' ], opts[ 'r' ], opts[ 'f' ], preops[ 'u' ],
			 &ruser, enviro, e );
	    doUnicode = ruser.Unicode();

	    if( e->Test() )
	        goto finish;

	    if( ruser.GotError() )
	        goto finish;

	    if( opts[ 'f' ] )
	        remote << "origin";
	    else
	        remote = *opts[ 'r' ];
	}
	else
	{
	    if( initValidate( opts[ 'c' ] ) )
	        goto finish;

	    if( opts[ 'p' ] && 
	      ( opts[ 'x' ] || opts [ 'n' ] || opts[ 'C' ] ) )
	    {
	        e->Set( initUsage );
	        goto finish;
	    }

	    s = enviro->Get( "P4CHARSET" );
	    doUnicode = ( s && StrPtr::CCompare( s, "none" ) );

	    sp = opts[ 'x' ];
	    if( sp && sp->Text()[0] == 'i' )
	    {
		doUnicode = 1;
		if( opts[ 'n' ] )
		{
		    e->Set( initUnicodeMixup );
	            goto finish;
		}
	    }
	    if( opts[ 'n' ] )
		doUnicode = 0;

	    if( opts[ 'p' ] || 
	     !( opts[ 'x' ] || opts [ 'n' ] || opts[ 'C' ] ) )
	    {
	        initDiscover( opts[ 'p' ], preops[ 'u' ], &ruser, enviro, e );

	        if( opts[ 'p' ] && e->Test() )
	            goto finish;

	        if( opts[ 'p' ] && ruser.GotError() )
	            goto finish;

	        if( e->Test() || ruser.GotError() )
	        {
	            // passive discovery failed, require options
	            ruser.ClearError();
	            e->Clear();

	            printf("No available server to discover configuration, needs flags:\n");
	            printf("p4 init -C0     (case-sensitive)\n");
	            printf("p4 init -C1     (case-insensitive)\n");
	            printf("Note, it is important to initialize with the same case sensitivity\n");
	            printf("as the server you wish to push/fetch from.\n");
	            goto finish;
	        }

	        doDiscover = 1;
	        doUnicode = ruser.Unicode();
	    }
	}

	if( ( sp = preops[ 'u' ] ) )
	    user.Set( sp );
	else
	    user = client->GetUser();

	if( ( sp = preops[ 'c' ] ) )
	    clientname.Set( sp );
	else
	{
	    DateTime dt;

	    dt.SetNow();
	    StrNum dn( dt.Value() );
	    clientname.Set( user );
	    clientname.UAppend( "-dvcs-" );
	    clientname.UAppend( &dn );
	}

	// Make it happen...

	// Get P4CONFIG, set if needed

	// Look for P4CONFIG file...
	fsys->Set( config );
	if( ( fsys->Stat() & ( FSF_EXISTS|FSF_EMPTY ) ) == FSF_EXISTS )
	{
	    delete fsys;
	    fsys = FileSys::Create( FST_ATEXT );
	    fsys->Set( config );
	}

	// Add values
	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;

	s = enviro->Get( "P4IGNORE" );
	if( s )
	    ignore.Set( s );
	fsys->Write( StrRef( "P4IGNORE=" ), e );
	fsys->Write( ignore, e );

	fsys->Write( StrRef( "\nP4CHARSET=" ), e );
	fsys->Write( StrRef( doUnicode ? "auto" : "none" ), e );

	fsys->Write( StrRef("\n"
	    "P4INITROOT=$configdir\n"
	    "P4USER=" ), e );
	fsys->Write( user, e );
# ifdef OS_NT
	fsys->Write( StrRef( "\nP4PORT=rsh:p4d.exe -i " ), e );
# else
	fsys->Write( StrRef( "\nP4PORT=rsh:/bin/sh -c \"umask 077 && exec p4d -i " ), e );
# endif
	if( preops[ 'v' ]  ) { fsys->Write( StrRef( "-v" ), e );
	                       fsys->Write( preops[ 'v' ], e ); }
	                else { fsys->Write( StrRef( "-J off" ), e ); }
# ifdef OS_NT
	fsys->Write( StrRef( " -r \"$configdir\\" INIT_ROOT "\"\n" ), e );
# else
	fsys->Write( StrRef( " -r '$configdir/" INIT_ROOT "'\"\n" ), e );
# endif
	fsys->Write( StrRef( "P4CLIENT=" ), e );
	fsys->Write( clientname, e );
	fsys->Write( "\n", 1, e );

	fsys->Close( e );

	curdir->SetLocal( *curdir, StrRef( INIT_ROOT ) );

	ra << "p4d";

	// Handle unicode

	if( doUnicode ) ra << "-xn";
	           else ra << "-xnn";

	ra << "-r" << *curdir;

	ra << "-u" << user;

	// Handle case flags

	if( opts[ 'C' ] )
	{
	    StrBuf caseflag( "-C" );
	    caseflag.UAppend( opts[ 'C' ] );
	    ra << caseflag;
	}
	else if( doClone || doDiscover )
	    ra << ruser.CaseFlag();

	// quiet
	ra << "-q";

	curdir->SetLocal( *curdir, fileref );
	fsys->Set( *curdir );

# ifndef OS_NT
	oldumask = umask( 077 );
# endif

	fsys->MkDir( e );

	// Make the .p4 root directory hidden

	curdir->ToParent();
	rootDir->Set( curdir->Text() );
	rootDir->SetAttribute( FSA_HIDDEN, e );

	fsys->Set( serverID );
	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;

	fsys->Write( clientname, e );
	fsys->Write( "\n", 1, e );
	fsys->Close( e );

	if( e->Test() )
	    goto finish;

	ecode = rc.Run( ra, e );

	if( ecode )
	{
	    // server failed to initialize

	    fsys->Set( config );
	    fsys->Unlink( e );

	    fsys->Set( serverID );
	    fsys->Unlink( e );
	    fsys->RmDir( e );

	    e->Set( initServerFail );
	    goto finish;
	}

	if( e->Test() )
	    goto finish;

# ifndef OS_NT
	umask( oldumask );
# endif

	if( !ecode )
	{
	    if( doClone )
	    {
	        cloneFetch( opts[ 'p' ], opts[ 'm' ], opts[ 'v' ], &remote, 
	                    &user, &ruser, &clientname, enviro, e );
	    }
	    else
	    {
	        if( doDiscover )
	        {
	            printf("Matching server configuration from '%s':\n", 
	                   ruser.Server().Text());
	            printf("%s, %s\n",
	                   ruser.CaseFlag() == "-C0" ? "case-sensitive (-C0)"
	                                             : "case-insensitive (-C1)",
	                   ruser.Unicode() ? "unicode (-xi)"
	                                   : "non-unicode (-n)" );
	            if( ruser.UserName() == "*unknown*" )
		        printf("warning: your user '%s' unknown at this server!\n",
		               ruser.GetUser().Text() );
	                
	        }
	        initService( &clientname, &ruser, opts[ 'c' ], enviro, e );
	    }

	    if( !e->Test() )
	        WriteIgnore( &ignore, &config, doClone, e );
	}

finish:
	if( e->Test() )
	{
	    cuser.Message( e );
	    ecode = 1;
	    e->Clear();
	}

	delete fsys;
	delete rootDir;
	delete curdir;
	delete client;
	delete enviro;

	return ecode;
}

void
initService( 
	StrPtr *clientname, 
	CloneUser *ruser, 
	StrPtr *branch, 
	Enviro *env,
	Error *e )
{
	Client client( env );
	char *args[2];
	ruser->GetData()->Clear();

	client.SetProtocol( P4Tag::v_tag );
	client.SetProtocol( P4Tag::v_enableStreams );
	client.Init( e );

	if( e->Test() )
	    return;

	*ruser->GetData() << "\nServerID: " << clientname;
	*ruser->GetData() << "\nType: server";
	*ruser->GetData() << "\nServices: local";
	*ruser->GetData() << "\nDescription:\n\tDVCS local personal repo\n";

	args[0] = (char *) "-i";
	ruser->SetCommand( "server-in" );
	client.SetArgv( 1, args );
	client.Run( "server", ruser );
	client.Final( e );

	client.SetProtocol( P4Tag::v_tag );
	client.SetProtocol( P4Tag::v_enableStreams );
	client.Init( e );

	if( e->Test() )
	    return;

	args[0] = (char *) "-i";
	if( branch )
	    args[1] = (char *) branch->Text();
	ruser->GetData()->Clear();
	ruser->SetCommand( "switch" );
	client.SetArgv( branch ? 2 : 1, args );
	client.Run( "switch", ruser );
	client.Final( e );
}

int
initValidate( StrPtr *branch )
{
	if( !branch )
	    return 0;

	char *p = branch->Text();
	int invalid = 0;

	if( strstr( p, "/" ) )
	{
	    // Must look like a stream
	    // e.g.   //depot/mainline

	    if( ( *p != '/' || *(p+1) != '/' || *(p+2) == '/' ) ||
	        !( p = strchr( p+2, '/' ) ) ||
	         ( strchr( p+1, '/' ) ) ||
	         ( *++p == 0 ) ) 
	        invalid = 1;
	}

	if( !invalid )
	    for( p = branch->Text(); !invalid && *p != 0; p++ )
	        invalid = ( *p == '@' || *p == '%' || *p == '#' || *p == '*' )
	                    || ( *p == '.' && *(p+1) == '.' && *(p+2) == '.' );
	if( !invalid )
	    return 0;

	printf("'%s' is not a valid mainline stream name.\n", branch->Text() );
	return 1;
}

void
initDiscover(
	StrPtr *port,
	StrPtr *user, 
	CloneUser *ruser, 
	Enviro *env,
	Error *e )
{
	Client client( env );
	char *args[2];

	client.SetProtocol( P4Tag::v_tag );
	client.SetProtocol( P4Tag::v_enableStreams );

	if( port )
	    client.SetPort( port );
	if( user )
	    client.SetUser( user );

	ruser->SetUser( client.GetUser().Text() );

	client.Init( e );

	if( e->Test() )
	    return;

	// Run info command, no crypto required (yet)

	ruser->SetCommand( "info-init" );
	client.Run( "info", ruser );
	client.Final( e );
}

void
cloneSetup( 
	StrPtr *port, 
	StrPtr *remote, 
	StrPtr *filepath, 
	StrPtr *user, 
	CloneUser *ruser, 
	Enviro *env,
	Error *e )
{
	Client client( env );
	char *args[2];

	client.SetProtocol( P4Tag::v_tag );
	client.SetProtocol( P4Tag::v_enableStreams );
	client.SetPort( port );
	if( user )
	    client.SetUser( user );
	client.Init( e );

	if( e->Test() )
	    return;

	// Run info command, no crypto required (yet)

	ruser->SetCommand( "info" );
	client.Run( "info", ruser );

	if( ruser->GotError() )
	{
	    // Only error is that push/fetch not allowed
	    printf("server '%s' not configured to clone (server.allowfetch).\n",
	           port->Text() );
	    client.Final( e );
	    return;
	}

	// Run remote command, crypto required

	if( remote )
	{
	    // Check remote exists

	    args[0] = (char *) "-E";
	    args[1] = remote->Text();
	    ruser->SetCommand( "remotes" );
	    client.SetArgv( 2, args );
	    client.Run( "remotes", ruser );

	    if( ruser->NeedLogin() )
	    {
	        printf("'%s' is not currently logged in to '%s'\n", 
	               client.GetUser().Text(), port->Text() );
	        printf("run 'p4 -u %s -p %s login' to authenticate\n",
	               client.GetUser().Text(), port->Text() );
	        client.Final( e );
	        ruser->SetError();
	        return;
	    }

	    if( !ruser->GotRemote() )
	    {
	        if( !ruser->GotError() )
	            printf("server '%s' does not have a remote spec '%s'\n",
	                   port->Text(), remote->Text() );
	        client.Final( e );
	        ruser->SetError();
	        return;
	    }

	    args[0] = (char *) "-o";
	    args[1] = remote->Text();
	    ruser->SetCommand( "remote-out" );
	    client.SetArgv( 2, args );
	    client.Run( "remote", ruser );
	    client.Final( e );
	}
	else if( filepath )
	{
	    // Fakeup a remote map called origin
	    // We can only map a filepath if its only wildcards are
	    // trailing ellipses.

	    StrBuf depotMap;
	    int cannotMap = 0;
	    int reMap = 0;
	    const char *s = filepath->Text();
	    const char *x = s + strlen( s );
	    const char *p = strstr( s, "/..." );
	    const char *newPath;

	    if( !p || p != ( x - 4 ) )
	        cannotMap = 1;

	    if( ruser->TooWide( s ) && cannotMap )
	    {
	        if( *s == '/' && *(s+1) == '/'  && strstr( (s+2), "/" ) 
	                      && *(x-1) != '/'  )
	        {
	            reMap = 1;
	            newPath = x;
	            while( *newPath != '/' ) 
	                newPath--;
	        }
	        else
	        {
	            printf( "filepath '%s' cannot be mapped.\n", 
	                    filepath->Text() );
	            client.Final( e );
	            ruser->SetError();
	            return;
	        }
	        
	    }
	    else if( ruser->TooWide( s ) )
	    {
	        printf( "filepath '%s' is too wide open.\n", filepath->Text() );
	        client.Final( e );
	        ruser->SetError();
	        return;
	    }

	    for( p = s; p < (x - 4); p++ )
	    {
                if( ( *p == '@' || *p == '%' || *p == '#' || *p == '*' )
                 || ( *p == '.' && *(p+1) == '.' && *(p+2) == '.' ) )
	        {
	            printf( "filepath '%s' contains %s.\n", filepath->Text(),
	                  (*p == '.') ? "embedded wildcards"
	                              : "illegal characters [@%#*]" );
	            client.Final( e );
	            ruser->SetError();
	            return;
	        }
	    }

	    args[0] = (char *) "-s";
	    ruser->SetCommand( "login-s" );
	    client.SetArgv( 1, args );
	    client.Run( "login", ruser );

	    if( ruser->NeedLogin() )
	    {
	        printf("'%s' is not currently logged in to '%s'\n", 
	               client.GetUser().Text(), port->Text() );
	        printf("run 'p4 -u %s -p %s login' to authenticate\n",
	               client.GetUser().Text(), port->Text() );
	        client.Final( e );
	        ruser->SetError();
	        return;
	    }

	    client.Final( e );

	    if( reMap )
	        depotMap << "//stream/main" << newPath << " " << filepath;
	    else if( cannotMap )
	        depotMap << filepath << " " << filepath;
	    else
	        depotMap << "//stream/main/..." << " " << filepath;

	    ruser->Dict()->SetVar( StrRef( "depotMap" ), depotMap );
	    ruser->SetDescription( "auto-generated from clone command" );
	}
}

void
cloneFetch( 
	StrPtr *port, 
	StrPtr *depth,
	StrPtr *debug,
	StrPtr *remote, 
	StrPtr *user, 
	CloneUser *ruser, 
	StrPtr *clientname,
	Enviro *env,
	Error *e )
{
	// save cloned remote map

	int i, j;
	StrRef var, val;
	Client client( env );
	char *args[1000];
	ruser->GetData()->Clear();

	client.SetProtocol( P4Tag::v_tag );
	client.SetProtocol( P4Tag::v_enableStreams );
	client.Init( e );

	if( e->Test() )
	{
	    client.Final( e );
	    return;
	}

	*ruser->GetData() << "\nServerID: " << clientname;
	*ruser->GetData() << "\nType: server";
	*ruser->GetData() << "\nServices: local";
	*ruser->GetData() << "\nDescription:\n\tDVCS local personal repo\n";

	args[0] = (char *) "-i";
	ruser->SetCommand( "server-in" );
	client.SetArgv( 1, args );
	client.Run( "server", ruser );
	client.Final( e );

	if( e->Test() || ruser->GotError() )
	    return;

	client.SetProtocol( P4Tag::v_tag );
	client.SetProtocol( P4Tag::v_enableStreams );
	client.Init( e );

	if( e->Test() )
	{
	    client.Final( e );
	    return;
	}

	ruser->GetData()->Clear();
	*ruser->GetData() << "\nRemoteID: origin";
	*ruser->GetData() << "\nAddress: " << port;
	*ruser->GetData() << "\nOwner: " << user;
	*ruser->GetData() << "\nOptions: unlocked nocompress";
	*ruser->GetData() << "\nDescription: ";

	char *ptr = ruser->Description().Text();

	while( *ptr != '\0' )
	{
	    ruser->GetData()->Append( ptr, 1 );
	    if( *ptr++ == '\n' && *ptr != '\0' )
	        *ruser->GetData() << "\t";
	}

	*ruser->GetData() << "\nDepotMap:\n";

	for( i = 0; ruser->Dict()->GetVar( i, var, val ); i++ )
	    *ruser->GetData() << "\t" << val.Text() << "\n";

	args[0] = (char *) "-i";

	ruser->SetCommand( "remote-in" );
	client.SetArgv( 1, args );
	client.Run( "remote", ruser );

	if( ruser->GotError() )
	{
	    client.Final( e );
	    return;
	}

	// Initialize depot, build mainline streams
	// Use the first map entry to create the first mainline

	int index = 0;
	int initDone = 0;
	int argsCount;
	StrBuf filePath;

	for( i = 0; ruser->Dict()->GetVar( i, var, val ); i++ )
	{
	    ruser->GetStreamName( &filePath, val );

	    if( ruser->StreamExists( filePath ) )
	        continue;

	    argsCount = 0;

	    if( initDone )
	    {
	        args[argsCount++] = (char *) "-m";
	        args[argsCount++] = (char *) "-c";
	    }
	    else
	    {
	        args[argsCount++] = (char *) "-i";
	        initDone++;
	    }

	    args[argsCount++] = (char *) "-s";
	    args[argsCount++] = (char *) ruser->Trim( filePath, val );

	    for( j = (i+1); ruser->Dict()->GetVar( j, var, val ); j++ )
	    {
	        if( !filePath.XCompareN( val ) )
	        {
	            args[argsCount++] = (char *) "-s";
	            args[argsCount++] = (char *) ruser->Trim( filePath, val );
	        }
	    }

	    args[argsCount++] = filePath.Text();
	    
	    ruser->SetCommand( "switch" );
	    client.SetArgv( argsCount, args );
	    client.Run( "switch", ruser );

	    // cleanup allocated trimmed paths

	    for( int x = 0; x < argsCount; ++x )
	    {
	        if( !strcmp( args[ x ], "-s" ) )
	            delete args[ ++x ];
	    }

	    if( ruser->GotError() )
	        break;
	}

	// Might have created an alternate depot,  need to
	// fire up a new connection

	client.Final( e );

	if( ruser->GotError() )
	    return;

	client.SetProtocol( P4Tag::v_tag );
	client.SetProtocol( P4Tag::v_enableStreams );
	client.Init( e );

	if( e->Test() )
	{
	    client.Final( e );
	    return;
	}

	printf("Cloning from '%s'...\n", port->Text());

	// Issue fetch command
	int argc = 0;

	if( debug )
	{
	    args[argc++] = (char *) "-v";
	}

	if( depth )
	{
	    args[argc++] = (char *) "-m";
	    args[argc++] = (char *) depth->Text();
	}

	ruser->SetCommand( "fetch" );
	client.SetArgv( argc, args );
	client.Run( "fetch", ruser );

	// Get MaxCommitChange.

	args[0] = (char *) "maxCommitChange";
	
	ruser->SetCommand( "counter" );
	client.SetArgv( 1, args );
	client.Run( "counter", ruser );

	// Only update LastPush if data was pulled

	if( !ruser->MaxChange() )
	{
	    client.Final( e );
	    return;
	}

	// Reload origin remote map.

	args[0] = (char *) "-o";
	args[1] = (char *) "origin";

	ruser->SetCommand( "remote-out2" );
	client.SetArgv( 2, args );
	client.Run( "remote", ruser );

	// Alter last push value

	ruser->GetData()->Clear();

	StrBuf map;
	map.Set( "DepotMap" );
	int mapCount = 0;

	for( i = 0; ruser->Dict()->GetVar( i, var, val ); i++ )
	{
	    // extra vars not in form

	    if( var == "specFormatted" || var == "func" )
	        continue; 
	    else if( var == "Description" )
	    {
	        char *ptr = val.Text();
	        *ruser->GetData() << var << ": ";

	        while( *ptr != '\0' )
	        {
	            ruser->GetData()->Append( ptr, 1 );
	            if( *ptr++ == '\n' && *ptr != '\0' )
	                *ruser->GetData() << "\t";
	        }
	    }
	    else if( !map.XCompareN( var ) )
	    {
	        if( !mapCount++ )
	            *ruser->GetData() << "DepotMap:\n";
	        *ruser->GetData() << "\t" << val << "\n";
	    }
	    else
	    {
	        *ruser->GetData() << var << ":\n\t";
	        if( var == "LastPush" )
	            *ruser->GetData() << ruser->MaxChange() << "\n";
	        else
	            *ruser->GetData() << val << "\n";
	    }
	}

	args[0] = (char *) "-i";

	ruser->SetCommand( "remote-in" );
	client.SetArgv( 1, args );
	client.Run( "remote", ruser );

	// End commands

	client.Final( e );
}

int
CloneUser::StreamExists( StrPtr &filePath )
{
	int i;
	StrRef var, val;

	for( i = 0; mainlines.GetVar( i, var, val ); i++ )
	{
	    if( !filePath.Compare( var ) )
	        return 1;
	}

	mainlines.SetVar( filePath, filePath );
	return 0;
}

const char *
CloneUser::Trim( StrPtr &filePath, StrPtr &val )
{
	StrBuf sharePath;

	char *p = val.Text() + filePath.Length() + 1;
	char *e = p;

	while( *e && *e != ' ' )
	    ++e;

	sharePath.Append( p, e - p );

	p = (char *) malloc( sharePath.Length() + 1 );
	strcpy( p, sharePath.Text() );
	return p;
}

void
CloneUser::GetStreamName( StrBuf *filePath, StrPtr &val )
{
	filePath->Clear();

	StrBuf buf;
	buf << val.Text();

	if( buf.Length() < 5 )
	    return;

	char *p, *s;

	s = buf.Text();
	if( *s == '-' ) s++;
	p = s + 2;
	p = strchr( p, '/' );

	if( !p || !*p )
	    return;

	p++;
	p = strchr( p, '/' );

	if( !p )
	    return;

	filePath->Append( s, p - s );
}

void
CloneUser::OutputStat( StrDict *varList )
{
	// Capture data from info and remote

	if( GetCommand() == "info" || GetCommand() == "info-init" )
	{
	    StrPtr *s;

	    unicode = varList->GetVar( P4Tag::v_unicode ) != 0;
	    security = varList->GetVar( P4Tag::v_security ) != 0;

	    s = varList->GetVar( P4Tag::v_userName );
	    if( s )
	        userName.Set( s );

	    s = varList->GetVar( P4Tag::v_serverAddress );
	    if( s )
	        serverAddress.Set( s );

	    s = varList->GetVar( P4Tag::v_caseHandling );

	    if( !s || *s == "sensitive"   ) caseFlag << "-C0";
	    else if(  *s == "insensitive" ) caseFlag << "-C1";
	    else if(  *s == "hybrid"      ) caseFlag << "-C2";

	    if( GetCommand() == "info" )
	    {
	        s = varList->GetVar( "allowFetch" );

	        if( !s || s->Atoi() < 2 )
	            commandError++;
	    }
	}
	else if( GetCommand() == "remote-out" )
	{
	    int i;
	    int count = 0;
	    int quoted;
	    StrRef var, val;
	    StrBuf lhs, depot, empty, map;
	    map.Set( "DepotMap" );
	    empty.Set( "..." );
	    const char *p;

	    for( i = 0; varList->GetVar( i, var, val ); i++ )
	    {
	        depot.Clear();

	        if( !map.XCompareN( var ) )
	        {
	            StrOps::GetDepotName( val.Text(), depot );

	            if( !empty.XCompareN( depot )  )
	            {
	                printf("Remote clone spec invalid!\n");
	                commandError++;
	            }

	            if( count++ )
	            {
	                if( depotName != depot )
	                {
	                    printf( "Remote spec references multiple depots on the left hand side of map:\n");
	                    for( int j = 0; varList->GetVar( j, var, val ); j++ )
	                        if( !map.XCompareN( var ) ) printf( "%s\n", val.Text() );
	                    commandError++;
	                    break;
	                }
	            }
	            else
	                depotName << depot;

	            // Validate stream(branch) side of map

	            lhs.Clear();

	            p = val.Text();
	            quoted = (*p++ == '"' );

	            while( *p != 0 )
	            {
	                if( ( quoted && *p == '"' ) ||
	                    (!quoted && *p == ' ' )  )
	                    break;
	                ++p;
	            }

	            if( quoted )
	                lhs.Append( val.Text()+1, (p - val.Text()) - 2 );
	            else
	                lhs.Append( val.Text(), p - val.Text() );

	            p = lhs.Text();
	            if( *p == '-' )
	               ++p;

	            if( TooWide( p ) )
	            {
	                printf("Remote map entry '%s' cannot be allocated a stream name:\n", lhs.Text() );
	                printf("Clone requires at least //<depotname>/<streamname>/ on the left hand side of the map\n");
	                commandError++;
	            }

	            remoteMap.SetVar( var, val );
	            continue;
	        }

	        if( var ==  "Description" )
	            description << val;
	    }
	}
	else if( GetCommand() == "remote-out2" )
	{
	    int i;
	    StrRef var, val;
	    remoteMap.Clear();

	    for( i = 0; varList->GetVar( i, var, val ); i++ )
	        remoteMap.SetVar( var, val );
	}
	else if( GetCommand() == "counter" )
	{
	    StrPtr *s = varList->GetVar( "value" );

	    if( s )
	        maxCommitChange = s->Atoi();
	}
	else if( GetCommand() == "remotes" )
	{
	    ++gotRemote;
	}
}

void
CloneUser::OutputText( const char *data, int length )
{
	printf("OutputText!\n");
}

void
CloneUser::OutputInfo( char level, const char *data )
{
	// quiet where possible

	if( quiet && ( GetCommand() == "remote-in" ||
	               GetCommand() == "switch" ) )
	    return;

	printf("%s\n", data);
}

void
CloneUser::OutputError( const char *errBuf )
{
	if( ( GetCommand() == "remotes" || GetCommand() == "login-s" )
	    && ( !strncmp( errBuf, "Perforce password", 17 ) || 
	         !strncmp( errBuf, "Your session has expired", 24 ) ) )
	{
	    needLogin++;
	}
	else
	{
	    printf( "%s", errBuf );
	    commandError++;
	}
}

void
CloneUser::InputData( StrBuf *buf, Error *e )
{
	if( GetCommand() == "remote-in" ||
	    GetCommand() == "server-in" )
	    buf->Set( inputData );
}

int
CloneUser::TooWide( const char *s )
{
	const char *p;
	return( *s != '/' || *(s+1) != '/' || *(s+2) == '/' ||
	         !( p = strstr(s+2,"/") )  || *(p+1) == '/' ||
	         !( p = strstr(p+1,"/") )  || *(p+1) == '\0' );
}

static void
WriteIgnore( StrPtr *ignore, StrPtr *config, int doClone, Error *e )
{
	FileSys *fsys;

	fsys = FileSys::Create( FST_TEXT );

	fsys->Set( *ignore );

	if( ( fsys->Stat() & ( FSF_EXISTS|FSF_EMPTY ) ) == FSF_EXISTS )
	{
	    // Might have been created by a failed clone, check before
	    // appending.

	    // Assume the clone fetch dumped it here... bail...

	    if( doClone )
	        goto finish;

	    fsys->Open( FOM_READ, e );

	    if( e->Test() )
	        goto finish;

	    StrBuf line;
	    while( fsys->ReadLine( &line, e  ) )
	    {
	        if( !line.Compare( StrRef( INIT_ROOT ) ) )
	        {
	            fsys->Close( e );
	            goto finish;
	        }
	    }

	    fsys->Close( e );
	    delete fsys;
	    fsys = FileSys::Create( FST_ATEXT );
	    fsys->Set( *ignore );
	}

	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;
 
	fsys->Write( *config, e );
	fsys->Write( "\n", 1, e );
	fsys->Write( *ignore, e );
	fsys->Write( StrRef( "\n.svn\n.git\n.DS_Store\n"
	             INIT_ROOT "\n*.swp\n" ), e );
	fsys->Close( e );

finish:
	delete fsys;
}

static void
LockCheck( Error *e )
{
	FileSys *fsys, *altsys;

	fsys = FileSys::Create( FST_BINARY );

	fsys->Set( "db.check" );
	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;

	altsys = FileSys::Create( FST_BINARY );

	altsys->Set( fsys->Name() );
	altsys->Perms( FPM_RW );
	altsys->Open( FOM_READ, e );

	if( !e->Test() )
	{
	    int ffd, afd;

	    ffd = fsys->GetFd();
	    afd = altsys->GetFd();

// Because Solaris locking is at a process level not a file descriptor
// level, we can't do all the checks we want

	    if( ! (
		    lockFile( ffd, LOCKF_EX_NB ) == 0 &&
# ifndef OS_SOLARIS
		    lockFile( afd, LOCKF_SH_NB ) == -1 &&
# endif
		    lockFile( ffd, LOCKF_UN ) == 0 &&
		    lockFile( afd, LOCKF_SH_NB ) == 0 &&
# ifndef OS_SOLARIS
		    lockFile( ffd, LOCKF_EX_NB ) == -1 &&
# endif
		    lockFile( afd, LOCKF_UN ) == 0 ) )
		e->Sys( "lockFile", "db.check" );

	    altsys->Close( e );
	}

	fsys->Close( e );

	delete altsys;

finish:
	fsys->Unlink( e );
	
	delete fsys;
}
# Change User Description Committed
#1 14945 Newtopian Merging

//guest/perforce_software/p4/...

to //guest/Newtopian/p4/...
//guest/perforce_software/p4/2015.1/client/clientinit.cc
#1 12190 Matt Attaway Initial drop of 2015.1 p4/p4api source