/* * 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; }