summaryrefslogtreecommitdiff
path: root/external/vpc/p4lib
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /external/vpc/p4lib
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'external/vpc/p4lib')
-rw-r--r--external/vpc/p4lib/p4.cpp2556
-rw-r--r--external/vpc/p4lib/p4lib.vpc43
2 files changed, 2599 insertions, 0 deletions
diff --git a/external/vpc/p4lib/p4.cpp b/external/vpc/p4lib/p4.cpp
new file mode 100644
index 0000000..d7e9796
--- /dev/null
+++ b/external/vpc/p4lib/p4.cpp
@@ -0,0 +1,2556 @@
+//====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======
+//
+// Purpose:
+//
+//=============================================================================
+
+// prevent Error function from being defined, since it inteferes with the P4 API
+#define Error Warning
+#include "p4lib/ip4.h"
+#include "tier1/utlvector.h"
+#include "tier1/utlsymbol.h"
+#include "tier1/utlbuffer.h"
+#include "tier1/utlstring.h"
+#include "tier1/utlmap.h"
+#include "tier1/characterset.h"
+
+#include "filesystem.h"
+#undef Error
+
+#undef Verify
+#include "clientapi.h"
+
+#include <time.h>
+#include <ctype.h>
+#include <io.h>
+#include <sys/stat.h>
+
+#include <windows.h>
+
+#define CLIENTSPEC_BUFFER_SIZE (16 * 1024)
+
+
+//-----------------------------------------------------------------------------
+// Stores info about the client path
+//-----------------------------------------------------------------------------
+struct CClientPathRecord
+{
+ char m_szDepotPath[MAX_PATH];
+ char m_szClientPath[MAX_PATH];
+ bool m_bNegative;
+};
+
+
+//-----------------------------------------------------------------------------
+// Global interfaces
+//-----------------------------------------------------------------------------
+#if defined(STANDALONE_VPC)
+#include "../utils/vpc/sys_utils.h"
+#define FileExists( arg ) Sys_Exists( arg )
+#define ReadFile( path, unused, buf ) Sys_LoadFileIntoBuffer( path, buf, false )
+#else
+static IFileSystem *g_pFileSystem;
+#define FileExists( arg ) g_pFileSystem->FileExists( arg )
+#define ReadFile( path, unused, buf ) g_pFilesystem->ReadFile( path, unused, buf )
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Interface to accessing P4 commands
+//-----------------------------------------------------------------------------
+class CP4 : public CBaseAppSystem<IP4>
+{
+public:
+ // Destructor
+ virtual ~CP4();
+
+ // Methods of IAppSystem
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual InitReturnVal_t Init();
+ virtual void *QueryInterface( const char *pInterfaceName );
+ virtual void Shutdown();
+ virtual void Disconnect();
+
+ // Inherited from IP4
+ P4Client_t &GetActiveClient();
+ virtual void RefreshActiveClient();
+ virtual void SetActiveClient(const char *clientname);
+ virtual void GetDepotFilePath(char *depotFilePath, const char *filespec, int size);
+ virtual void GetClientFilePath(char *clientFilePath, const char *filespec, int size);
+ virtual void GetLocalFilePath(char *localFilePath, const char *filespec, int size);
+ virtual CUtlVector<P4File_t> &GetFileList(const char *path);
+ virtual CUtlVector<P4File_t> &GetFileListUsingClientSpec( const char *pPath, const char *pClientSpec );
+ virtual void GetOpenedFileList( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly );
+ virtual void GetOpenedFileList( const char *pRootDirectory, CUtlVector<P4File_t> &fileList );
+ virtual void GetOpenedFileListInPath( const char *pPathID, CUtlVector<P4File_t> &fileList );
+ virtual void GetFileListInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList );
+
+ virtual CUtlVector<P4Revision_t> &GetRevisionList(const char *path, bool bIsDir);
+ virtual CUtlVector<P4Client_t> &GetClientList();
+ virtual void RemovePathFromActiveClientspec(const char *path);
+ virtual void SetOpenFileChangeList( const char *pChangeListName );
+ virtual bool OpenFileForAdd( const char *fullpath );
+ virtual bool OpenFileForEdit(const char *fullpath);
+ virtual bool OpenFileForDelete(const char *fullpath);
+ virtual bool SyncFile( const char *pFullPath, int nRevision = -1 );
+ virtual bool SubmitFile( const char *pFullPath, const char *pDescription );
+ virtual bool RevertFile( const char *pFullPath );
+ virtual bool OpenFilesForAdd( int nCount, const char **ppFullPathList );
+ virtual bool OpenFilesForEdit( int nCount, const char **ppFullPathList );
+ virtual bool OpenFilesForDelete( int nCount, const char **ppFullPathList );
+ virtual bool SubmitFiles( int nCount, const char **ppFullPathList, const char *pDescription );
+ virtual bool RevertFiles( int nCount, const char **ppFullPathList );
+ virtual bool IsFileInPerforce( const char *fullpath );
+ virtual P4FileState_t GetFileState( const char *pFullPath );
+ virtual bool GetFileInfo( const char *pFullPath, P4File_t *pFileInfo );
+
+ virtual const char *GetDepotRoot();
+ virtual int GetDepotRootLength();
+ virtual const char *GetLocalRoot();
+ virtual int GetLocalRootLength();
+ virtual const char *String( CUtlSymbol s ) const;
+ virtual bool GetClientSpecForFile( const char *pFullPath, char *pClientSpec, int nMaxLen );
+ virtual bool GetClientSpecForDirectory( const char *pFullPathDir, char *pClientSpec, int nMaxLen );
+ virtual bool GetClientSpecForPath( const char *pPathId, char *pClientSpec, int nMaxLen );
+ virtual void OpenFileInP4Win( const char *pFullPath );
+ virtual bool IsConnectedToServer( bool bRetry = true );
+ virtual const char *GetLastError();
+ char const * GetOpenFileChangeListNum(); // Returns NULL for default
+
+ // Convert a depot file to a local file user the current client mapping
+ bool DepotFileToLocalFile( const char *pDepotFile, char *pLocalFile, int nBufLen );
+
+ // Accessors
+ ClientApi &GetClientApi() { return m_Client; }
+ ClientUser &GetClientUser() { return m_User; }
+
+private:
+ // Helper for operations on multiple files
+ typedef void (*PerforceOp_t)( int nCount, const char **ppFullPathList, const char *pDescription );
+ bool PerformPerforceOp( PerforceOp_t op, int nCount, const char **ppFullPathList, const char *pDescription );
+ bool PerformPerforceOp( const char *pOperation, int nCount, const char **ppFullPathList );
+ bool PerformPerforceOp( const char *pOperation, const char *pFullPath );
+
+ bool PerformPerforceOpCurChangeList( const char *pOperation, int nCount, const char **ppFullPathList );
+ bool PerformPerforceOpCurChangeList( const char *pOperation, const char *pFullPath );
+
+ void RefreshClientData();
+
+ bool m_bConnectedToServer;
+ P4Client_t m_ActiveClient;
+ CUtlVector< CClientPathRecord > m_ClientMapping;
+ char m_szDepotRoot[_MAX_PATH];
+ char m_szLocalRoot[_MAX_PATH];
+ int m_iDepotRootLength;
+ int m_iLocalRootLength;
+ CUtlString m_sChangeListName, m_sCachedChangeListNum;
+ int m_nCachedChangeListNumber;
+
+ // internal
+ ClientApi m_Client;
+ ClientUser m_User;
+};
+
+
+//-----------------------------------------------------------------------------
+// global instance
+//-----------------------------------------------------------------------------
+CP4 s_p4;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CP4, IP4, P4_INTERFACE_VERSION, s_p4 );
+
+
+//-----------------------------------------------------------------------------
+// Scoped class to push and pop a particular client spec
+//-----------------------------------------------------------------------------
+class CScopedClientSpec
+{
+public:
+ CScopedClientSpec( const char *pClientSpec );
+ ~CScopedClientSpec();
+
+private:
+ char m_pOldClientSpec[MAX_PATH];
+ bool m_bClientSpecNeedsRestore;
+};
+
+CScopedClientSpec::CScopedClientSpec( const char *pClientSpec )
+{
+ m_bClientSpecNeedsRestore = false;
+ V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
+ if ( V_stricmp( m_pOldClientSpec, pClientSpec ) )
+ {
+ s_p4.SetActiveClient( pClientSpec );
+ m_bClientSpecNeedsRestore = true;
+ }
+}
+
+CScopedClientSpec::~CScopedClientSpec()
+{
+ if ( m_bClientSpecNeedsRestore )
+ {
+ s_p4.SetActiveClient( m_pOldClientSpec );
+ }
+}
+
+
+class CScopedFileClientSpec
+{
+public:
+ CScopedFileClientSpec( const char *pFullPath );
+ ~CScopedFileClientSpec();
+
+private:
+ char m_pOldClientSpec[MAX_PATH];
+ bool m_bClientSpecNeedsRestore;
+};
+
+CScopedFileClientSpec::CScopedFileClientSpec( const char *pFullPath )
+{
+ m_bClientSpecNeedsRestore = false;
+ char pClientSpec[MAX_PATH];
+ if ( s_p4.GetClientSpecForFile( pFullPath, pClientSpec, sizeof(pClientSpec) ) )
+ {
+ V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
+ if ( V_stricmp( m_pOldClientSpec, pClientSpec ) )
+ {
+ s_p4.SetActiveClient( pClientSpec );
+ m_bClientSpecNeedsRestore = true;
+ }
+ }
+}
+
+CScopedFileClientSpec::~CScopedFileClientSpec()
+{
+ if ( m_bClientSpecNeedsRestore )
+ {
+ s_p4.SetActiveClient( m_pOldClientSpec );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Scoped class to push and pop a particular client spec
+//-----------------------------------------------------------------------------
+class CScopedDirClientSpec
+{
+public:
+ CScopedDirClientSpec( const char *pFullPathDir );
+ ~CScopedDirClientSpec();
+
+private:
+ char m_pOldClientSpec[MAX_PATH];
+ bool m_bClientSpecNeedsRestore;
+};
+
+CScopedDirClientSpec::CScopedDirClientSpec( const char *pFullPathDir )
+{
+ m_bClientSpecNeedsRestore = false;
+ char pClientSpec[MAX_PATH];
+ if ( s_p4.GetClientSpecForDirectory( pFullPathDir, pClientSpec, sizeof(pClientSpec) ) )
+ {
+ V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
+ if ( V_stricmp( m_pOldClientSpec, pClientSpec ) )
+ {
+ s_p4.SetActiveClient( pClientSpec );
+ m_bClientSpecNeedsRestore = true;
+ }
+ }
+}
+
+CScopedDirClientSpec::~CScopedDirClientSpec()
+{
+ if ( m_bClientSpecNeedsRestore )
+ {
+ s_p4.SetActiveClient( m_pOldClientSpec );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Scoped class to push and pop a particular client spec
+//-----------------------------------------------------------------------------
+class CScopedPathClientSpec
+{
+public:
+ CScopedPathClientSpec( const char *pPathID );
+ ~CScopedPathClientSpec();
+
+private:
+ char m_pOldClientSpec[MAX_PATH];
+ bool m_bClientSpecNeedsRestore;
+};
+
+CScopedPathClientSpec::CScopedPathClientSpec( const char *pPathID )
+{
+ m_bClientSpecNeedsRestore = false;
+ char pClientSpec[MAX_PATH];
+ if ( s_p4.GetClientSpecForPath( pPathID, pClientSpec, sizeof(pClientSpec) ) )
+ {
+ V_strncpy( m_pOldClientSpec, s_p4.GetClientApi().GetClient().Text(), sizeof(m_pOldClientSpec) );
+ s_p4.SetActiveClient( pClientSpec );
+ m_bClientSpecNeedsRestore = true;
+ }
+}
+
+CScopedPathClientSpec::~CScopedPathClientSpec()
+{
+ if ( m_bClientSpecNeedsRestore )
+ {
+ s_p4.SetActiveClient( m_pOldClientSpec );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: utility function to split a typical P4 line output into var and value
+//-----------------------------------------------------------------------------
+static void SplitP4Output(const char *data, char *pszCmd, char *pszInfo, int bufLen)
+{
+ V_strncpy(pszCmd, data, bufLen);
+
+ char *mid = V_strstr(pszCmd, " ");
+ if (mid)
+ {
+ *mid = 0;
+ V_strncpy(pszInfo, data + (mid - pszCmd) + 1, bufLen);
+ }
+ else
+ {
+ pszInfo[0] = 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: base class for parse input from the P4 server
+//-----------------------------------------------------------------------------
+template< class T >
+class CDataRetrievalUser : public ClientUser
+{
+public:
+ CUtlVector<T> &GetData()
+ {
+ return m_Data;
+ }
+
+ // call this to start retrieving data
+ void InitRetrievingData()
+ {
+ m_bAwaitingNewRecord = true;
+ m_pData = &m_Data;
+ m_pData->RemoveAll();
+ }
+
+ // call this to start retrieving data into an external piece of memory
+ void InitRetrievingData( CUtlVector<T> *pData )
+ {
+ m_bAwaitingNewRecord = true;
+ m_pData = pData;
+ m_pData->RemoveAll();
+ }
+
+ T *ForceNextRecord()
+ {
+ int index = m_pData->AddToTail();
+ if ( m_pData->Count() > 1 )
+ {
+ m_pData->Element(index) = m_pData->Element(0);
+ }
+ return &m_pData->Element(index);
+ }
+ // implement this to parse out input from the server into the specified object
+ virtual void OutputRecord(T &obj, const char *pszVar, const char *pszInfo) = 0;
+
+
+private:
+ bool m_bAwaitingNewRecord;
+ CUtlVector<T> m_Data;
+ CUtlVector<T> *m_pData;
+
+ virtual void OutputInfo( char level, const char *data )
+ {
+ if ( V_strlen(data) < 1 )
+ {
+ // end of a record, await the new one
+ m_bAwaitingNewRecord = true;
+ return;
+ }
+
+ if ( m_bAwaitingNewRecord )
+ {
+ // add in the new record
+ T &record = m_pData->Element( m_pData->AddToTail() );
+ memset( &record, 0, sizeof( record ) );
+ Construct( &record );
+ m_bAwaitingNewRecord = false;
+ }
+
+ // parse
+ char szVar[_MAX_PATH];
+ char szInfo[_MAX_PATH];
+ SplitP4Output( data, szVar, szInfo, sizeof( szVar ) );
+
+ // emit
+ T &record = m_pData->Element( m_pData->Count() - 1 );
+ OutputRecord( record, szVar, szInfo );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Retrieves a file list
+//-----------------------------------------------------------------------------
+class CFileUser : public CDataRetrievalUser<P4File_t>
+{
+public:
+ void RetrieveDir(const char *dir)
+ {
+ // clear the list
+ InitRetrievingData();
+
+ // search for the paths - this string gets all revisions of a file,
+ // from the version they have locally to the current version
+ // this is so we can see if the local files are out of date
+ char szSearch[MAX_PATH];
+ V_snprintf(szSearch, sizeof(szSearch), "%s/*", dir);
+
+ // gets directories first
+ char *argv[] = { "-C", (char *)szSearch, NULL };
+ s_p4.GetClientApi().SetArgv( 2, argv );
+ s_p4.GetClientApi().Run("dirs", this);
+
+ // now get files, '-Rc' restricts files to client mapping
+ char *argv2[] = { "-Rc", "-Op", (char *)szSearch, NULL };
+ s_p4.GetClientApi().SetArgv( 3, argv2 );
+ s_p4.GetClientApi().Run("fstat", this);
+
+ ComputeLocalDirectoryNames( GetData() );
+ }
+
+ void RetrieveFile(const char *filespec)
+ {
+ // clear the list
+ InitRetrievingData();
+
+ // now get files, '-Rc' restricts files to client mapping
+ char *argv2[] = { "-Rc", "-Op", (char *)filespec, NULL };
+ s_p4.GetClientApi().SetArgv( 3, argv2 );
+ s_p4.GetClientApi().Run("fstat", this);
+ ComputeLocalDirectoryNames( GetData() );
+ }
+ // Show how file names map through the client view
+ // For each file in filespec, three names are produced:
+ // depotFile the name in the depot
+ // clientFile the name on the client in Perforce syntax
+ // localFile the name on the client in local syntax
+ void RetrieveWhereabouts(const char *filespec)
+ {
+ // clear the list
+ InitRetrievingData();
+
+ // figure out where filespec is
+ char *argv[] = { (char *)filespec, NULL };
+ s_p4.GetClientApi().SetArgv( 1, argv );
+ s_p4.GetClientApi().Run("where", this);
+ }
+
+ void RetrieveOpenedFiles( const char *filespec )
+ {
+ // clear the list
+ InitRetrievingData();
+ char *argv[] = { (char *)filespec, NULL };
+ s_p4.GetClientApi().SetArgv( 1, argv );
+ s_p4.GetClientApi().Run("opened", this);
+ ComputeLocalFileNames( GetData() );
+ }
+
+ void BeginChangelistProcess()
+ {
+ m_bChangesRecord = true;
+ m_nChangesIndex = 0;
+ }
+ void EndChangelistProcess()
+ {
+ m_bChangesRecord = false;
+ m_nChangesIndex = -1;
+ }
+
+ void RetrieveOpenedFiles( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly )
+ {
+ // clear the list
+ InitRetrievingData( &fileList );
+ if ( bDefaultChangeOnly )
+ {
+ char *argv[] = { (char *)"-c", (char*)"default", NULL };
+ s_p4.GetClientApi().SetArgv( 2, argv );
+ }
+ s_p4.GetClientApi().Run( "opened", this );
+ ComputeLocalFileNames( fileList );
+ }
+
+ void RetrieveFilesInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList )
+ {
+ BeginChangelistProcess();
+ InitRetrievingData( &fileList );
+ char changeListString[32];
+ V_snprintf( changeListString, sizeof(changeListString), "%d", changeListNumber );
+ char *argv[] = { (char *)"-s", changeListString, NULL };
+ s_p4.GetClientApi().SetArgv( 2, argv );
+ s_p4.GetClientApi().Run( "describe", this );
+ EndChangelistProcess();
+ }
+
+
+private:
+ void ComputeLocalFileNames( CUtlVector<P4File_t> &fileList )
+ {
+ // Need to construct valid local paths since opened doesn't do it for us
+ char pMatchPattern[MAX_PATH];
+ V_snprintf( pMatchPattern, sizeof(pMatchPattern), "//%s/", s_p4.GetClientApi().GetClient().Text() );
+ int nLen = V_strlen( pMatchPattern );
+
+ int nCount = fileList.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( fileList[i].m_bDir )
+ continue;
+
+ const char *pClientPath = s_p4.String( fileList[i].m_sClientFile );
+ if ( V_stristr( pClientPath, pMatchPattern ) != pClientPath )
+ continue;
+
+ char pLocalPath[MAX_PATH];
+ V_ComposeFileName( s_p4.GetLocalRoot(), pClientPath + nLen, pLocalPath, sizeof(pLocalPath) );
+ V_FixSlashes( pLocalPath );
+ fileList[i].m_sLocalFile = pLocalPath;
+ }
+ }
+
+ void ComputeLocalDirectoryNames( CUtlVector<P4File_t> &fileList )
+ {
+ // Need to construct valid local paths since opened doesn't do it for us
+ int nCount = fileList.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !fileList[i].m_bDir )
+ continue;
+
+ char pLocalPath[MAX_PATH];
+ const char *pDepotPath = s_p4.String( fileList[i].m_sDepotFile );
+ if ( !s_p4.DepotFileToLocalFile( pDepotPath, pLocalPath, sizeof(pLocalPath) ) )
+ continue;
+
+ fileList[i].m_sLocalFile = pLocalPath;
+ }
+ }
+
+ void OutputRecordInternal(P4File_t &file, const char *szCmd, const char *szInfo)
+ {
+ if (!V_strcmp(szCmd, "headRev"))
+ {
+ file.m_iHeadRevision = atoi(szInfo);
+ }
+ else if (!V_strcmp(szCmd, "haveRev"))
+ {
+ file.m_iHaveRevision = atoi(szInfo);
+ }
+ else if (!V_strcmp(szCmd, "headAction") && !V_strcmp(szInfo, "delete"))
+ {
+ file.m_bDeleted = true;
+ }
+ else if (!V_strcmp(szCmd, "action"))
+ {
+ if (!V_strcmp(szInfo, "edit"))
+ {
+ file.m_eOpenState = P4FILE_OPENED_FOR_EDIT;
+ }
+ else if (!V_strcmp(szInfo, "delete"))
+ {
+ file.m_eOpenState = P4FILE_OPENED_FOR_DELETE;
+ }
+ else if (!V_strcmp(szInfo, "add"))
+ {
+ file.m_eOpenState = P4FILE_OPENED_FOR_ADD;
+ }
+ else if (!V_strcmp(szInfo, "integrate"))
+ {
+ file.m_eOpenState = P4FILE_OPENED_FOR_INTEGRATE;
+ }
+ }
+ else if (!V_strcmp(szCmd, "change"))
+ {
+ file.m_iChangelist = atoi(szInfo);
+ }
+ else if ( !V_strcmp( szCmd, "otherOpen" ) )
+ {
+ file.m_bOpenedByOther = atoi( szInfo ) != 0;
+ }
+ else if ( m_bChangesRecord && (!V_strcmp(szCmd, "user") || !V_strcmp(szCmd,"client") || !V_strcmp(szCmd, "time") ||
+ !V_strcmp(szCmd, "desc") || !V_strcmp(szCmd, "status") || !V_strcmp(szCmd, "oldChange") ||
+ !V_strcmp(szCmd,"type") || !V_strcmp(szCmd,"rev")) )
+ {
+ // ignore these, processing changelist description
+ }
+ else
+ {
+ // extract the path
+ char pFilePath[_MAX_PATH];
+ V_strncpy( pFilePath, szInfo, sizeof( pFilePath ) );
+ char *pFileName = V_strrchr(pFilePath, '/');
+ if (pFileName)
+ {
+ ++pFileName;
+ }
+
+ if ( !V_stricmp( szCmd, "dir" ) )
+ {
+ // new directory, add to the list
+ file.m_sPath = pFilePath;
+ file.m_sDepotFile = pFilePath;
+ file.m_sName = pFileName;
+ file.m_bDir = true;
+ }
+ else if ( !V_strcmp( szCmd, "depotFile" ) )
+ {
+ if ( m_bChangesRecord )
+ {
+ char pLocalPath[MAX_PATH];
+ if ( s_p4.DepotFileToLocalFile( szInfo, pLocalPath, sizeof(pLocalPath) ) )
+ {
+ file.m_sLocalFile = pLocalPath;
+ }
+ }
+ char *pCruft = V_strstr( pFilePath, "//%%1" );
+ if (pCruft)
+ *pCruft = 0;
+ file.m_sDepotFile = pFilePath;
+ // Now that we've stored off the depot file, split file into path + name
+ if (pFileName)
+ {
+ *(pFileName - 1) = 0;
+ }
+
+ file.m_sPath = pFilePath;
+ file.m_sName = pFileName;
+ file.m_bDir = false;
+ }
+ else if (!V_stricmp(szCmd, "clientFile"))
+ {
+ char *pCruft = V_strstr( pFilePath, "//%%1" );
+ if (pCruft)
+ *pCruft = 0;
+ file.m_sClientFile = pFilePath;
+ }
+ else if (!V_stricmp(szCmd, "path"))
+ {
+ char *pCruft = V_strstr(pFilePath, "\\%%1");
+ if (pCruft)
+ *pCruft = 0;
+ file.m_sLocalFile = pFilePath;
+ }
+ }
+ }
+ virtual void OutputRecord(P4File_t &file, const char *szCmd, const char *szInfo)
+ {
+ P4File_t *pFile = &file;
+ if ( m_bChangesRecord )
+ {
+ char tmpCmd[1024];
+ int end = V_strlen(szCmd)-1;
+ if ( end < ARRAYSIZE(tmpCmd) )
+ {
+ const char *pChar = szCmd + end;
+ while ( isdigit(*pChar) && pChar > szCmd )
+ {
+ pChar--;
+ }
+ int pos = pChar - szCmd;
+ if ( pos > 0 && pos < end )
+ {
+ int newChangeIndex = atoi(pChar+1);
+ V_strncpy( tmpCmd, szCmd, sizeof(tmpCmd) );
+ tmpCmd[pos+1] = 0;
+ szCmd = tmpCmd;
+ if ( newChangeIndex != m_nChangesIndex )
+ {
+ pFile = ForceNextRecord();
+ m_nChangesIndex = newChangeIndex;
+ }
+ }
+ }
+ }
+ OutputRecordInternal( *pFile, szCmd, szInfo );
+ }
+
+ int m_nChangesIndex;
+ bool m_bChangesRecord;
+};
+CFileUser g_FileUser;
+CFileUser g_WhereUser;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Retrieves a history list
+//-----------------------------------------------------------------------------
+class CRevisionHistoryUser : public CDataRetrievalUser<P4Revision_t>
+{
+public:
+ void RetrieveHistory(const char *path, bool bDir)
+ {
+ InitRetrievingData();
+
+ // search for the paths - this string gets all revisions of a file,
+ // from the version they have locally to the current version
+ // this is so we can see if the local files are out of date
+ char szSearch[MAX_PATH];
+ if (bDir)
+ {
+ V_snprintf(szSearch, sizeof(szSearch), "%s/...", path);
+ }
+ else
+ {
+ V_snprintf(szSearch, sizeof(szSearch), "%s", path);
+ }
+
+ // set to view long output, to show the time, and to have a maximum number of results
+ char *argv[] = { "-l", "-t", "-m", "50", (char *)szSearch, NULL };
+ s_p4.GetClientApi().SetArgv( 5, argv );
+ s_p4.GetClientApi().Run("changes", this);
+
+ }
+
+private:
+ virtual void OutputRecord(P4Revision_t &revision, const char *szCmd, const char *szInfo)
+ {
+ if (!V_strcmp(szCmd, "change"))
+ {
+ revision.m_iChange = atoi(szInfo);
+ }
+ else if (!V_strcmp(szCmd, "user"))
+ {
+ revision.m_sUser = szInfo;
+ }
+ else if (!V_strcmp(szCmd, "client"))
+ {
+ revision.m_sClient = szInfo;
+ }
+ else if (!V_strcmp(szCmd, "status"))
+ {
+ }
+ else if (!V_strcmp(szCmd, "desc"))
+ {
+ revision.m_Description = szInfo;
+ }
+ else if (!V_strcmp(szCmd, "time"))
+ {
+ int64 iTime = atoi(szInfo);
+
+ struct tm *gmt = gmtime(reinterpret_cast<time_t*>(&iTime));
+
+ revision.m_nYear = gmt->tm_year + 1900;
+ revision.m_nMonth = gmt->tm_mon + 1;
+ revision.m_nDay = gmt->tm_mday;
+ revision.m_nHour = gmt->tm_hour;
+ revision.m_nMinute = gmt->tm_min;
+ revision.m_nSecond = gmt->tm_sec;
+ }
+ }
+};
+CRevisionHistoryUser g_RevisionHistoryUser;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Retreives list of clients
+//-----------------------------------------------------------------------------
+class CClientSpecUser : public CDataRetrievalUser<P4Client_t>
+{
+public:
+ void RetrieveClients()
+ {
+ InitRetrievingData();
+
+ // we just get the entire list
+ s_p4.GetClientApi().Run("clients", this);
+ }
+
+private:
+ virtual void OutputRecord(P4Client_t &client, const char *szCmd, const char *szInfo)
+ {
+ if (!V_strcmp(szCmd, "client"))
+ {
+ client.m_sName = szInfo;
+ }
+ else if (!V_strcmp(szCmd, "Owner"))
+ {
+ client.m_sUser = szInfo;
+ }
+ else if (!V_strcmp(szCmd, "Root"))
+ {
+ client.m_sLocalRoot = szInfo;
+ }
+ else if (!V_strcmp(szCmd, "Host"))
+ {
+ client.m_sHost = szInfo;
+ }
+ else
+ {
+ Msg("Unknown field %s = %s\n", szCmd, szInfo);
+ }
+ }
+};
+CClientSpecUser g_ClientspecUser;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: holder and modifier of clientspec file mapings
+//-----------------------------------------------------------------------------
+class CClientspecMap
+{
+public:
+ char m_szFullClientspec[CLIENTSPEC_BUFFER_SIZE];
+ int m_iFullClientspecWritePosition;
+ CUtlVector<CClientPathRecord> m_PathMap;
+ char m_szLocalPath[_MAX_PATH];
+
+ void ResetFullClientSpec( void )
+ {
+ m_szFullClientspec[0] = '\0';
+ m_iFullClientspecWritePosition = 0;
+ m_PathMap.RemoveAll();
+ m_szLocalPath[0] = '\0';
+ }
+
+ void AddVarToFullClientSpec( const char *variable, const char *data )
+ {
+ V_snprintf( &m_szFullClientspec[m_iFullClientspecWritePosition], CLIENTSPEC_BUFFER_SIZE - m_iFullClientspecWritePosition, "%s: %s\n\n", variable, data );
+ m_iFullClientspecWritePosition += strlen( &m_szFullClientspec[m_iFullClientspecWritePosition] );
+ }
+
+ void AddStringToFullClientSpec( const char *szString )
+ {
+ V_strncpy( &m_szFullClientspec[m_iFullClientspecWritePosition], szString, CLIENTSPEC_BUFFER_SIZE - m_iFullClientspecWritePosition );
+ m_iFullClientspecWritePosition += strlen( &m_szFullClientspec[m_iFullClientspecWritePosition] );
+ }
+
+ void ReadRoot( const char *clientRoot )
+ {
+ V_strncpy( m_szLocalPath, clientRoot, _MAX_PATH );
+ }
+
+ void ReadViewLine( const char *viewLine )
+ {
+ CUtlBuffer parse(viewLine, V_strlen(viewLine), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
+
+ CClientPathRecord record;
+ record.m_bNegative = false;
+
+ // get the depot path
+ parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
+ if (V_strlen(record.m_szDepotPath) < 1)
+ return;
+
+ if (!V_stricmp(record.m_szDepotPath, "-"))
+ {
+ // it's the negation sign, get the next line
+ parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
+ record.m_bNegative = true;
+ }
+ if (record.m_szDepotPath[0] == '-')
+ {
+ // negation sign is stuck on the end, remove
+ record.m_bNegative = true;
+ memmove(record.m_szDepotPath, record.m_szDepotPath + 1, sizeof(record.m_szDepotPath) - 1);
+ }
+
+ // get the next sign
+ parse.GetString(record.m_szClientPath, sizeof(record.m_szClientPath));
+
+ // append to list
+ m_PathMap.AddToTail(record);
+ }
+
+ void ReadClientspec(const char *clientspec)
+ {
+ // Get the local path
+ char *pszSearch = "\n\nRoot:";
+ char *pStr = V_strstr( clientspec, pszSearch );
+ if (pStr)
+ {
+ pStr += V_strlen( pszSearch );
+
+ // parse out next satring
+ CUtlBuffer parse(pStr, V_strlen(pStr), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
+
+ // get the local path
+ parse.GetString( m_szLocalPath, sizeof(m_szLocalPath) );
+ }
+
+ pszSearch = "\n\nView:\n";
+ pStr = V_strstr(clientspec, pszSearch);
+ if (pStr)
+ {
+ pStr += V_strlen(pszSearch);
+
+ // take a full copy of the string
+ V_strncpy(m_szFullClientspec, clientspec, sizeof(m_szFullClientspec));
+
+ // parse out strings
+ CUtlBuffer parse(pStr, V_strlen(pStr), CUtlBuffer::TEXT_BUFFER|CUtlBuffer::READ_ONLY);
+
+ while (parse.IsValid())
+ {
+ CClientPathRecord record;
+ record.m_bNegative = false;
+
+ // get the depot path
+ parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
+ if (V_strlen(record.m_szDepotPath) < 1)
+ break;
+
+ if (!V_stricmp(record.m_szDepotPath, "-"))
+ {
+ // it's the negation sign, get the next line
+ parse.GetString(record.m_szDepotPath, sizeof(record.m_szDepotPath));
+ record.m_bNegative = true;
+ }
+ if (record.m_szDepotPath[0] == '-')
+ {
+ // negation sign is stuck on the end, remove
+ record.m_bNegative = true;
+ memmove(record.m_szDepotPath, record.m_szDepotPath + 1, sizeof(record.m_szDepotPath) - 1);
+ }
+
+ // get the next sign
+ parse.GetString(record.m_szClientPath, sizeof(record.m_szClientPath));
+
+ // append to list
+ m_PathMap.AddToTail(record);
+ }
+ }
+ }
+
+ void WriteClientspec(char *pszDest, int destSize)
+ {
+ // assemble the new view
+ char szView[CLIENTSPEC_BUFFER_SIZE] = { 0 };
+ for (int i = 0; i < m_PathMap.Count(); i++)
+ {
+ CClientPathRecord &record = m_PathMap[i];
+
+ V_strncat(szView, " ", sizeof(szView), COPY_ALL_CHARACTERS);
+ if (record.m_bNegative)
+ {
+ V_strncat(szView, "-", sizeof(szView), COPY_ALL_CHARACTERS);
+ }
+ V_strncat(szView, record.m_szDepotPath, sizeof(szView), COPY_ALL_CHARACTERS);
+ V_strncat(szView, " ", sizeof(szView), COPY_ALL_CHARACTERS);
+ V_strncat(szView, record.m_szClientPath, sizeof(szView), COPY_ALL_CHARACTERS);
+ V_strncat(szView, " \n", sizeof(szView), COPY_ALL_CHARACTERS);
+ }
+
+ // find the place the view is set
+ char *pszSearch = "\n\nView:\n";
+ char *pStr = V_strstr(m_szFullClientspec, pszSearch);
+ if (pStr)
+ {
+ char *pDest = (pStr + strlen(pszSearch));
+
+ // replace the view with the new view
+ V_strncpy(pDest, szView, sizeof(m_szFullClientspec) - (pDest - m_szFullClientspec));
+ }
+
+ // emit
+ V_strncpy(pszDest, m_szFullClientspec, destSize);
+ }
+
+ void RemovePathFromClient(const char *path)
+ {
+ // work out how the path record contains the specified path
+ for (int i = 0; i < m_PathMap.Count(); i++)
+ {
+ // look for the first path that matches, comparing the two strings up to before the "/..." in szDepotPath
+ if (!V_strnicmp(m_PathMap[i].m_szDepotPath, path, V_strlen(m_PathMap[i].m_szDepotPath) - 4))
+ {
+ CClientPathRecord &baseRecord = m_PathMap[i];
+
+ // we have a match, build the negative case
+ CClientPathRecord negativeRecord;
+ negativeRecord.m_bNegative = true;
+ V_snprintf(negativeRecord.m_szDepotPath, sizeof(negativeRecord.m_szDepotPath), "%s/...", path);
+
+ // build the clientspec side of the mapping
+
+ // aa/b/... //client/BAH/...
+ //- aa/b/d/... //client/BAH/d/
+
+ // find the common strings in both
+ int iPos = 0;
+ while (baseRecord.m_szDepotPath[iPos] == negativeRecord.m_szDepotPath[iPos])
+ ++iPos;
+
+ char *pszNegPaths = negativeRecord.m_szDepotPath + iPos;
+
+ // append the neg paths to the existing baseRecord.szClientPath
+ V_strncpy(negativeRecord.m_szClientPath, baseRecord.m_szClientPath, sizeof(negativeRecord.m_szClientPath));
+ // strip off the last slash
+ char *pszDir = V_strstr(negativeRecord.m_szClientPath, "/...");
+ if (pszDir)
+ {
+ *pszDir = 0;
+ }
+
+ // append the new client paths to negate
+ V_strncat(negativeRecord.m_szClientPath, "/", sizeof(negativeRecord.m_szClientPath), COPY_ALL_CHARACTERS);
+ V_strncat(negativeRecord.m_szClientPath, pszNegPaths, sizeof(negativeRecord.m_szClientPath), COPY_ALL_CHARACTERS);
+
+ m_PathMap.InsertAfter(i, negativeRecord);
+ break;
+ }
+ }
+ }
+
+ // calculates the depot root
+ void GetCommonDepotRoot(char *pszDest, int destSize)
+ {
+ int nCount = m_PathMap.Count();
+ if ( nCount == 0 )
+ {
+ pszDest[0] = 0;
+ return;
+ }
+
+ // walk each mapping, and find the longest common starting substring
+ V_strncpy(pszDest, m_PathMap[0].m_szDepotPath, destSize);
+ for (int i = 1; i < nCount; i++)
+ {
+ if ( m_PathMap[i].m_bNegative )
+ continue;
+
+ // see how much we compare
+ for (char *pszD = pszDest, *pszS = m_PathMap[i].m_szDepotPath; *pszD && *pszS; ++pszD, ++pszS)
+ {
+ if (*pszD != *pszS)
+ {
+ *pszD = 0;
+ break;
+ }
+ }
+ }
+
+ // remove any "..." at the end
+ char *pszD = V_strstr(pszDest, "...");
+ if (pszD)
+ {
+ *pszD = 0;
+ }
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: tool for editing clientspecs
+//-----------------------------------------------------------------------------
+class CClientspecEditUser : public ClientUser
+{
+public:
+ CClientspecEditUser( void ) : m_bReadDataVar( false ) {};
+ void RetrieveClient(const char *pszClient)
+ {
+ m_Map.ResetFullClientSpec();
+
+ // we just get the entire list
+ char *argv[] = { "-o", (char *)pszClient, NULL };
+ s_p4.GetClientApi().SetArgv( 2, argv );
+ s_p4.GetClientApi().Run("client", this);
+ }
+
+ // saves out the new path spec to the
+ void WriteClientspec()
+ {
+ // write out the new clientspec
+ char *argv[] = { "-i", NULL, NULL };
+ s_p4.GetClientApi().SetArgv( 1, argv );
+ s_p4.GetClientApi().Run("client", this);
+ }
+
+ CClientspecMap m_Map;
+ bool m_bReadDataVar; //if we encountered a "data" variable, turn off the other parsing
+
+private:
+ virtual void OutputInfo( char level, const char *data )
+ {
+ char szVar[CLIENTSPEC_BUFFER_SIZE], szInfo[CLIENTSPEC_BUFFER_SIZE];
+ SplitP4Output(data, szVar, szInfo, sizeof(szInfo));
+
+ if( szVar[0] != '\0' )
+ {
+ if (!V_stricmp(szVar, "data"))
+ {
+ m_bReadDataVar = true;
+ m_Map.ReadClientspec(szInfo);
+ }
+ else if( !m_bReadDataVar )
+ {
+ if( !V_stricmp( szVar, "root" ) )
+ {
+ m_Map.ReadRoot( szInfo );
+ m_Map.AddVarToFullClientSpec( szVar, szInfo );
+ }
+ else if( !V_strnicmp( szVar, "view", 4 ) )
+ {
+ if( !V_stricmp( szVar, "view" ) || !V_stricmp( szVar, "view0" ) )
+ {
+ //cheat a bit
+ m_Map.AddStringToFullClientSpec( "View:\n" );
+ }
+ m_Map.ReadViewLine( szInfo );
+ m_Map.AddStringToFullClientSpec( "\t" );
+ m_Map.AddStringToFullClientSpec( szInfo );
+ m_Map.AddStringToFullClientSpec( "\n" );
+ }
+ else
+ {
+ m_Map.AddVarToFullClientSpec( szVar, szInfo );
+ }
+ }
+ }
+ }
+
+ // called to read data
+ virtual void InputData( StrBuf *strbuf, Error *e )
+ {
+ char szView[CLIENTSPEC_BUFFER_SIZE];
+ m_Map.WriteClientspec(szView, sizeof(szView));
+ strbuf->Set(szView);
+ e->Clear();
+ }
+
+ virtual void OutputError( const char *errBuf )
+ {
+ Msg("s_p4 error: %s", errBuf);
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Retreives list of clients
+//-----------------------------------------------------------------------------
+class CInfoUser : public ClientUser
+{
+public:
+ void RetrieveInfo()
+ {
+ memset(&m_Client, 0, sizeof(m_Client));
+
+ // we just get the entire list
+ s_p4.GetClientApi().Run("info", this);
+ }
+
+ P4Client_t m_Client;
+
+private:
+ virtual void OutputInfo(char level, const char *data)
+ {
+ char szCmd[_MAX_PATH];
+ char szInfo[_MAX_PATH];
+
+ SplitP4Output(data, szCmd, szInfo, sizeof(szCmd));
+
+ if (!V_stricmp(szCmd, "userName"))
+ {
+ m_Client.m_sUser = szInfo;
+ }
+ else if (!V_stricmp(szCmd, "clientName"))
+ {
+ m_Client.m_sName = szInfo;
+ }
+ else if (!V_stricmp(szCmd, "clientHost"))
+ {
+ m_Client.m_sHost = szInfo;
+ }
+ else if (!V_stricmp(szCmd, "clientRoot"))
+ {
+ m_Client.m_sLocalRoot = szInfo;
+ }
+ }
+};
+CInfoUser g_InfoUser;
+
+
+// Changelist description structure
+struct ChangelistDesc_t
+{
+ int id;
+ time_t m_tTimeStamp;
+ CUtlSymbol m_sUser;
+ CUtlSymbol m_sClient;
+ CUtlSymbol m_sStatus;
+ CUtlSymbol m_sDescription;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: tool for creating new changelists
+//-----------------------------------------------------------------------------
+class CChangelistCreateUser : public CDataRetrievalUser<int>
+{
+public:
+ void CreateChangelist( const char *pDescription )
+ {
+ InitRetrievingData();
+
+ // Will force InputData to be called
+ m_pDescription = ( pDescription && pDescription[0] ) ? pDescription : "I'm a loser who didn't type a description. Mock me at your earliest convenience.";
+ char *argv[] = { "-i", NULL };
+ s_p4.GetClientApi().SetArgv( 1, argv );
+ s_p4.GetClientApi().Run("change", this);
+ }
+
+private:
+ // called to read data from stdin
+ virtual void InputData( StrBuf *strbuf, Error *e )
+ {
+ char svChangelist[ P4_MAX_INPUT_BUFFER_SIZE + 2048 ];
+ P4Client_t &activeClient = s_p4.GetActiveClient();
+
+ V_snprintf( svChangelist, sizeof(svChangelist),
+ "Change:\tnew\n\nClient:\t%s\n\nUser:\t%s\n\nStatus:\tnew\n\n"
+ "Description:\n\t%s\n\nFiles:\n\n",
+ activeClient.m_sName.String(), activeClient.m_sUser.String(), m_pDescription );
+
+ strbuf->Set(svChangelist);
+ e->Clear();
+ }
+
+ virtual void OutputRecord( int &nChangelist, const char *szCmd, const char *szInfo)
+ {
+ if (!V_strcmp(szCmd, "Change"))
+ {
+ nChangelist = atoi(szInfo);
+ }
+ }
+
+ const char *m_pDescription;
+};
+CChangelistCreateUser g_ChangelistCreateUser;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: tool for finding an existing changelist
+//-----------------------------------------------------------------------------
+class CChangelistFindUser : public CDataRetrievalUser<ChangelistDesc_t>
+{
+public:
+ void ListChangelists( void )
+ {
+ InitRetrievingData();
+
+ char *argv[] = { "-l", "-t", "-s", "pending", "-c", s_p4.GetClientApi().GetClient().Text(), NULL };
+ s_p4.GetClientApi().SetArgv( 6, argv );
+ s_p4.GetClientApi().Run("changes", this);
+ }
+
+private:
+ virtual void OutputRecord( ChangelistDesc_t &cl, const char *szCmd, const char *szInfo)
+ {
+ if ( !V_strcmp( szCmd, "change" ) )
+ {
+ cl.id = atoi( szInfo );
+ }
+ else if ( !V_strcmp( szCmd, "time" ) )
+ {
+ cl.m_tTimeStamp = atoi( szInfo );
+ }
+ else if ( !V_strcmp( szCmd, "user" ) )
+ {
+ cl.m_sUser = szInfo;
+ }
+ else if ( !V_strcmp( szCmd, "client" ) )
+ {
+ cl.m_sClient = szInfo;
+ }
+ else if ( !V_strcmp( szCmd, "status" ) )
+ {
+ cl.m_sStatus = szInfo;
+ }
+ else if ( !V_strcmp( szCmd, "desc" ) )
+ {
+ CUtlVector<char> arrInfo;
+ arrInfo.SetCount( 1 + strlen( szInfo ) );
+ memcpy( arrInfo.Base(), szInfo, arrInfo.Count() );
+ for ( int nCount = arrInfo.Count() - 1; nCount -- > 0; )
+ {
+ if ( isspace( arrInfo[ nCount ] ) )
+ arrInfo[ nCount ] = 0;
+ else
+ break;
+ }
+
+ cl.m_sDescription = arrInfo.Base();
+ }
+ }
+};
+CChangelistFindUser g_ChangelistFindUser;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: only used to handle the error callback
+//-----------------------------------------------------------------------------
+class CErrorHandlerUser : public ClientUser
+{
+public:
+ CErrorHandlerUser() : m_errorSeverity( E_EMPTY ), m_uiFlags( 0 ) {}
+
+ virtual void OutputError( const char *errBuf )
+ {
+ m_errorSeverity = max( m_errorSeverity, E_WARN ); // this is a guess - it could have been E_FATAL or E_FAILED
+
+ Msg("s_p4 error: %s", errBuf);
+
+ m_errorBuf.Append( errBuf );
+ }
+ virtual void HandleError( Error *err )
+ {
+ m_errorSeverity = max( m_errorSeverity, ( ErrorSeverity )err->GetSeverity() );
+
+ StrBuf buf;
+ err->Fmt( buf, EF_NEWLINE );
+
+ if ( V_strstr( buf.Text(), "can't edit exclusive file already opened" ) )
+ {
+ m_errorSeverity = max( m_errorSeverity, E_WARN ); // s_p4 sends this is an info message, even though the file doesn't get edited!
+ }
+
+ if ( ( m_uiFlags & eDisableFiltering ) || ShallOutputErrorStringBuffer( buf ) )
+ Msg( "%s: %s", err->FmtSeverity(), buf.Text() );
+
+ m_errorBuf.Append( &buf );
+ }
+
+ // Message is the only method that gets called on a s_p4 error
+ // even though the s_p4 documentation claims that HandleError (and therefore OutputError)
+ // should get called via the defalt Message implementation
+ virtual void Message( Error *err )
+ {
+ HandleError( err );
+ }
+
+ void ResetErrorState()
+ {
+ m_errorSeverity = E_EMPTY;
+ m_errorBuf.Clear();
+ }
+ ErrorSeverity GetErrorState()
+ {
+ return m_errorSeverity;
+ }
+ const char *GetErrorString()
+ {
+ return m_errorBuf.Text();
+ }
+ void SetErrorString( const char *errStr, ErrorSeverity severity )
+ {
+ m_errorBuf.Set( errStr );
+ m_errorSeverity = severity;
+ }
+
+ enum Flags {
+ eDisableFiltering = 1 << 0, // Disable filtering output
+ eFilterClUselessSpew = 1 << 1, // Filter "already opened", "add of exisiting", "can't change" msgs
+ };
+
+ // Sets the new flag mask, add prevails over remove if the flag specified in both.
+ // SetFlags( x, 0 ) - enables flag x
+ // SetFlags( 0, x ) - removes flag x
+ // SetFlags( mask, ~0 ) - completely sets the flag mask
+ uint32 SetFlags( uint32 uiAdd, uint32 uiRemove ) { uint32 uiOld = m_uiFlags; m_uiFlags = ( ( m_uiFlags & ~uiRemove ) | uiAdd ); return uiOld; }
+
+protected:
+ // Performs filtering before the message is output to screen,
+ // if "ShallOutputErrorStringBuffer" returns false, then the message
+ // is not printed on screen, otherwise the modified strbug is printed.
+ virtual bool ShallOutputErrorStringBuffer( StrBuf &buf );
+
+private:
+ ErrorSeverity m_errorSeverity;
+ StrBuf m_errorBuf;
+ uint32 m_uiFlags;
+};
+CErrorHandlerUser g_ErrorHandlerUser;
+
+bool CErrorHandlerUser::ShallOutputErrorStringBuffer( StrBuf &buf )
+{
+ char const *pText = buf.Text();
+
+ if ( m_uiFlags & eFilterClUselessSpew )
+ {
+ if ( V_strstr( pText, "already opened for edit" ) )
+ return false;
+ if ( V_strstr( pText, "already opened for add" ) )
+ return false;
+ if ( V_strstr( pText, "currently opened for edit" ) )
+ return false;
+ if ( V_strstr( pText, "currently opened for add" ) )
+ return false;
+ if ( V_strstr( pText, "add of existing file" ) )
+ return false;
+ if ( V_strstr( pText, "add existing file" ) )
+ return false;
+ if ( V_strstr( pText, "can't change from" ) )
+ return false;
+ if ( V_strstr( pText, "no such file" ) )
+ return false;
+ if ( V_strstr( pText, "not on client" ) )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: tool for creating new changelists
+//-----------------------------------------------------------------------------
+class CSubmitUser : public ClientUser
+{
+public:
+ void Submit( int nChangeList )
+ {
+ // NOTE: -i doesn't appear to work with -c
+ // We have to set up the description when creating the changelist in the first place
+ m_pDescription = NULL;
+ char pBuf[128];
+ V_snprintf( pBuf, sizeof(pBuf), "%d", nChangeList );
+ char *argv[] = { "-c", pBuf, NULL };
+ s_p4.GetClientApi().SetArgv( 2, argv );
+ s_p4.GetClientApi().Run( "submit", &g_ErrorHandlerUser );
+ }
+
+private:
+ // called to read data from stdin
+ virtual void InputData( StrBuf *strbuf, Error *e )
+ {
+ Assert( m_pDescription );
+
+ char svChangelist[1024];
+ P4Client_t &activeClient = s_p4.GetActiveClient();
+
+ V_snprintf( svChangelist, sizeof(svChangelist),
+ "Change:\tnew\n\nClient:\t%s\n\nUser:\t%s\n\nStatus:\tnew\n\n"
+ "Description:\n\t%s\n\nFiles:\n\n",
+ activeClient.m_sName.String(), activeClient.m_sUser.String(), m_pDescription );
+
+ strbuf->Set(svChangelist);
+ e->Clear();
+ }
+
+ const char *m_pDescription;
+};
+CSubmitUser g_SubmitUser;
+
+
+int FindOrCreateChangelist( const char *pDescription )
+{
+ int iCreatedEmptyCl = 0, iFoundChangelist = 0;
+ CUtlMap<int, ChangelistDesc_t const *> mapFoundChangelists( DefLessFunc( int ) );
+
+ CUtlSymbol symDesc = pDescription;
+
+find_changelists:
+ mapFoundChangelists.RemoveAll();
+ g_ChangelistFindUser.ListChangelists();
+ for ( int k = 0; k < g_ChangelistFindUser.GetData().Count(); ++ k )
+ {
+ ChangelistDesc_t const &cl = g_ChangelistFindUser.GetData()[ k ];
+
+ if ( symDesc == cl.m_sDescription )
+ mapFoundChangelists.Insert( cl.id, &cl );
+ }
+
+ if ( mapFoundChangelists.Count() )
+ iFoundChangelist = mapFoundChangelists.Key( mapFoundChangelists.FirstInorder() );
+ else if ( iCreatedEmptyCl > 0 )
+ return iCreatedEmptyCl;
+
+ if ( iFoundChangelist > 0 )
+ {
+ // Check if we created a changelist that has to be deleted
+ if ( iCreatedEmptyCl > 0 && iCreatedEmptyCl != iFoundChangelist )
+ {
+ // Delete changelist
+ char chClNumber[50];
+ sprintf( chClNumber, "%d", iCreatedEmptyCl );
+ char *argv[] = { "-d", chClNumber, NULL };
+ s_p4.GetClientApi().SetArgv( 2, argv );
+ s_p4.GetClientApi().Run( "change", &g_ErrorHandlerUser );
+ }
+
+ return iFoundChangelist;
+ }
+ else
+ {
+ // We did not find a changelist, create a new one
+ g_ChangelistCreateUser.CreateChangelist( pDescription );
+
+ iCreatedEmptyCl = g_ChangelistCreateUser.GetData().Count() ? g_ChangelistCreateUser.GetData()[0] : 0;
+ if ( !iCreatedEmptyCl )
+ return 0;
+
+ // Now we have a changelist created, check again to avoid multithreading bugs
+ goto find_changelists;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Destructor
+//-----------------------------------------------------------------------------
+CP4::~CP4()
+{
+ // Prevents a hang if Shutdown isn't called before the process exits
+ if ( m_bConnectedToServer )
+ {
+ Shutdown();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Connect, disconnect
+//-----------------------------------------------------------------------------
+bool CP4::Connect( CreateInterfaceFn factory )
+{
+#if !defined(STANDALONE_VPC)
+ g_pFileSystem = (IFileSystem*)factory( FILESYSTEM_INTERFACE_VERSION, NULL );
+ return ( g_pFileSystem != NULL );
+#else
+ return true;
+#endif
+}
+
+void CP4::Disconnect()
+{
+#if !defined(STANDALONE_VPC)
+ g_pFileSystem = NULL;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Startup
+//-----------------------------------------------------------------------------
+InitReturnVal_t CP4::Init()
+{
+ // set the protocol return all data as key/value pairs
+ m_Client.SetProtocol( "tag", "" );
+
+ // connect to the s_p4 server
+ Error e;
+ m_Client.Init( &e );
+ m_bConnectedToServer = e.Test() == 0 && m_Client.Dropped() == 0;
+
+ RefreshClientData();
+
+ return INIT_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Cleanup
+//-----------------------------------------------------------------------------
+void CP4::Shutdown()
+{
+ Error e;
+ m_Client.Final(&e);
+ m_bConnectedToServer = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: refreshes client data
+//-----------------------------------------------------------------------------
+void CP4::RefreshClientData()
+{
+ if ( !m_bConnectedToServer )
+ return;
+
+ // retrieve our login info
+ g_InfoUser.RetrieveInfo();
+ m_ActiveClient = g_InfoUser.m_Client;
+
+ // calculate our common depot root
+ CClientspecEditUser user;
+ user.RetrieveClient( GetActiveClient().m_sName.String() );
+
+ user.m_Map.GetCommonDepotRoot(m_szDepotRoot, sizeof(m_szDepotRoot));
+ m_iDepotRootLength = V_strlen(m_szDepotRoot);
+ m_ClientMapping = user.m_Map.m_PathMap;
+ V_strncpy( m_szLocalRoot, user.m_Map.m_szLocalPath, sizeof(m_szLocalRoot) );
+ m_iLocalRootLength = V_strlen(m_szLocalRoot);
+
+ SetOpenFileChangeList( m_sChangeListName.String() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Convert a depot file to a local file user the current client mapping
+//-----------------------------------------------------------------------------
+bool CP4::DepotFileToLocalFile( const char *pDepotFile, char *pLocalPath, int nBufLen )
+{
+ // Need to construct valid local paths since opened doesn't do it for us
+ char pClientPattern[MAX_PATH];
+ V_snprintf( pClientPattern, sizeof(pClientPattern), "//%s/", m_Client.GetClient().Text() );
+ int nClientLen = V_strlen( pClientPattern );
+
+ int nMatchCount = 0;
+ int nCount = m_ClientMapping.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ const char *pDepotPath = m_ClientMapping[i].m_szDepotPath;
+ int nLen = V_strlen( pDepotPath );
+ if ( nMatchCount > nLen )
+ continue;
+
+ bool bRecursive = !V_stricmp( &pDepotPath[nLen-4], "/..." );
+ bool bInDirectory = !V_stricmp( &pDepotPath[nLen-2], "/*" );
+ if ( !bRecursive && !bInDirectory )
+ continue;
+
+ // FIXME: Do this fixup when we create m_ClientMapping?
+ char pMatchingString[MAX_PATH];
+ V_strncpy( pMatchingString, pDepotPath, bRecursive ? nLen-2 : nLen );
+ if ( V_stristr( pDepotFile, pMatchingString ) != pDepotFile )
+ continue;
+
+ // Skip subdirectories if it's in the directory
+ if ( bInDirectory )
+ {
+ const char *pRelativePath = pDepotFile + nLen - 1;
+ if ( strchr( pRelativePath, '\\' ) || strchr( pRelativePath, '/' ) )
+ continue;
+ }
+
+ const char *pClientPath = s_p4.String( m_ClientMapping[i].m_szClientPath );
+ int nPathLen = V_strlen( pClientPath );
+
+ bool bClientRecursive = !V_stricmp( &pClientPath[nPathLen-4], "/..." );
+ bool bClientInDirectory = !V_stricmp( &pClientPath[nPathLen-2], "/*" );
+ if ( !bClientRecursive && !bClientInDirectory )
+ continue;
+
+ char pTruncatedClientPath[MAX_PATH];
+ V_strncpy( pTruncatedClientPath, pClientPath, bClientRecursive ? nPathLen-2 : nPathLen );
+ if ( V_stristr( pTruncatedClientPath, pClientPattern ) != pTruncatedClientPath )
+ continue;
+
+ // This is necessary if someone has a trailing slash on their root as in c:\valve\main\
+ // Otherwise it'll return something like c:\valve\main\\src\blah.cpp and confuse VPC.
+ char szLocalRootWithoutSlashes[MAX_PATH];
+ V_strncpy( szLocalRootWithoutSlashes, s_p4.GetLocalRoot(), sizeof( szLocalRootWithoutSlashes ) );
+ V_StripTrailingSlash( szLocalRootWithoutSlashes );
+
+ if ( nClientLen < nPathLen - 3 )
+ {
+ V_snprintf( pLocalPath, nBufLen, "%s\\%s%s", szLocalRootWithoutSlashes, pTruncatedClientPath + nClientLen, bRecursive ? pDepotFile + nLen - 3 : pDepotFile + nLen - 1 );
+ }
+ else
+ {
+ V_snprintf( pLocalPath, nBufLen, "%s\\%s", szLocalRootWithoutSlashes, bRecursive ? pDepotFile + nLen - 3 : pDepotFile + nLen - 1 );
+ }
+ V_FixSlashes( pLocalPath );
+ nMatchCount = nLen;
+ }
+
+ return ( nMatchCount > 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Refreshes the current client from s_p4 settings
+//-----------------------------------------------------------------------------
+void CP4::RefreshActiveClient()
+{
+ if ( !IsConnectedToServer() )
+ return;
+
+ RefreshClientData();
+}
+
+
+//-----------------------------------------------------------------------------
+// Query interface
+//-----------------------------------------------------------------------------
+void *CP4::QueryInterface( const char *pInterfaceName )
+{
+ return Sys_GetFactoryThis()( pInterfaceName, NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets a string for a symbol
+//-----------------------------------------------------------------------------
+const char *CP4::String( CUtlSymbol s ) const
+{
+ return s.String();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: data accessor
+//-----------------------------------------------------------------------------
+const char *CP4::GetDepotRoot()
+{
+ if ( !IsConnectedToServer() )
+ return NULL;
+
+ return m_szDepotRoot;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: data accessor
+//-----------------------------------------------------------------------------
+int CP4::GetDepotRootLength()
+{
+ if ( !IsConnectedToServer() )
+ return -1;
+
+ return m_iDepotRootLength;
+}
+
+const char *CP4::GetLocalRoot()
+{
+ if ( !IsConnectedToServer() )
+ return NULL;
+
+ return m_szLocalRoot;
+}
+
+int CP4::GetLocalRootLength()
+{
+ if ( !IsConnectedToServer() )
+ return -1;
+
+ return m_iLocalRootLength;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: translates filespecs to depotFile paths
+//-----------------------------------------------------------------------------
+void CP4::GetDepotFilePath(char *depotFilePath, const char *filespec, int size)
+{
+ if ( !IsConnectedToServer() )
+ {
+ if ( size > 0 )
+ {
+ depotFilePath[ 0 ] = '\0';
+ }
+ return;
+ }
+
+ g_WhereUser.RetrieveWhereabouts(filespec);
+ V_strncpy(depotFilePath, g_WhereUser.GetData()[0].m_sDepotFile.String(), size);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: translates filespecs to clientFile paths
+//-----------------------------------------------------------------------------
+void CP4::GetClientFilePath(char *clientFilePath, const char *filespec, int size)
+{
+ if ( !IsConnectedToServer() )
+ {
+ if ( size > 0 )
+ {
+ clientFilePath[ 0 ] = '\0';
+ }
+ return;
+ }
+
+ g_WhereUser.RetrieveWhereabouts(filespec);
+ V_strncpy(clientFilePath, g_WhereUser.GetData()[0].m_sClientFile.String(), size);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: translates filespecs to localFile paths
+//-----------------------------------------------------------------------------
+void CP4::GetLocalFilePath(char *localFilePath, const char *filespec, int size)
+{
+ if ( !IsConnectedToServer() )
+ {
+ if ( size > 0 )
+ {
+ localFilePath[ 0 ] = '\0';
+ }
+ return;
+ }
+
+ g_WhereUser.RetrieveWhereabouts(filespec);
+ V_strncpy(localFilePath, g_WhereUser.GetData()[0].m_sLocalFile.String(), size);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns a list of files
+//-----------------------------------------------------------------------------
+CUtlVector<P4File_t> &CP4::GetFileList( const char *pPath )
+{
+ if ( !IsConnectedToServer() )
+ {
+ static CUtlVector< P4File_t > dummy;
+ return dummy;
+ }
+
+ CScopedDirClientSpec spec( pPath );
+ g_FileUser.RetrieveDir( pPath );
+ return g_FileUser.GetData();
+}
+
+
+//-----------------------------------------------------------------------------
+// retreives the list of files in a path, using a known client spec
+//-----------------------------------------------------------------------------
+CUtlVector<P4File_t> &CP4::GetFileListUsingClientSpec( const char *pPath, const char *pClientSpec )
+{
+ if ( !IsConnectedToServer() )
+ {
+ static CUtlVector< P4File_t > dummy;
+ return dummy;
+ }
+
+ CScopedClientSpec spec( pClientSpec );
+ g_FileUser.RetrieveDir( pPath );
+ return g_FileUser.GetData();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the list of files opened for edit/integrate/delete
+//-----------------------------------------------------------------------------
+void CP4::GetOpenedFileList( CUtlVector<P4File_t> &fileList, bool bDefaultChangeOnly )
+{
+ if ( !IsConnectedToServer() )
+ {
+ fileList.RemoveAll();
+ return;
+ }
+
+ g_FileUser.RetrieveOpenedFiles( fileList, bDefaultChangeOnly );
+}
+
+void CP4::GetFileListInChangelist( unsigned int changeListNumber, CUtlVector<P4File_t> &fileList )
+{
+ if ( !IsConnectedToServer() )
+ {
+ fileList.RemoveAll();
+ return;
+ }
+ g_FileUser.RetrieveFilesInChangelist( changeListNumber, fileList );
+}
+
+
+void CP4::GetOpenedFileList( const char *pRootDirectory, CUtlVector<P4File_t> &fileList )
+{
+ if ( !IsConnectedToServer() )
+ {
+ fileList.RemoveAll();
+ return;
+ }
+
+ CScopedDirClientSpec spec( pRootDirectory );
+ g_FileUser.RetrieveOpenedFiles( fileList, false );
+}
+
+void CP4::GetOpenedFileListInPath( const char *pPathID, CUtlVector<P4File_t> &fileList )
+{
+ if ( !IsConnectedToServer() )
+ {
+ fileList.RemoveAll();
+ return;
+ }
+
+ CScopedPathClientSpec spec( pPathID );
+ g_FileUser.RetrieveOpenedFiles( fileList, false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: file history
+//-----------------------------------------------------------------------------
+CUtlVector<P4Revision_t> &CP4::GetRevisionList(const char *path, bool bIsDir)
+{
+ if ( !IsConnectedToServer() )
+ {
+ static CUtlVector< P4Revision_t > dummy;
+ return dummy;
+ }
+
+ g_RevisionHistoryUser.RetrieveHistory(path, bIsDir);
+ return g_RevisionHistoryUser.GetData();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns a list of clients
+//-----------------------------------------------------------------------------
+CUtlVector<P4Client_t> &CP4::GetClientList()
+{
+ if ( !IsConnectedToServer() )
+ {
+ static CUtlVector< P4Client_t > dummy;
+ return dummy;
+ }
+
+ g_ClientspecUser.RetrieveClients();
+ return g_ClientspecUser.GetData();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets the active client
+//-----------------------------------------------------------------------------
+void CP4::SetActiveClient(const char *clientname)
+{
+ if ( !IsConnectedToServer() )
+ return;
+
+ m_Client.SetClient(clientname);
+ RefreshClientData();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the name of the currently opened clientspec
+//-----------------------------------------------------------------------------
+P4Client_t &CP4::GetActiveClient()
+{
+ return m_ActiveClient;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: cloaks a folder from the current view
+//-----------------------------------------------------------------------------
+void CP4::RemovePathFromActiveClientspec(const char *path)
+{
+ if ( !IsConnectedToServer() )
+ return;
+
+ // read in the clientspec
+ CClientspecEditUser user;
+ user.RetrieveClient(GetActiveClient().m_sName.String());
+
+ // get the extra info
+ user.m_Map.RemovePathFromClient(path);
+
+ // refresh the list
+ user.WriteClientspec();
+}
+
+
+//-----------------------------------------------------------------------------
+// Finds a client spec for a directory. Is destructive to the passed-in directory
+//-----------------------------------------------------------------------------
+bool CP4::GetClientSpecForDirectory( const char *pFullPathDir, char *pClientSpec, int nMaxLen )
+{
+ if ( nMaxLen == 0 )
+ return false;
+
+ pClientSpec[ 0 ] = '\0';
+
+ if ( !IsConnectedToServer() )
+ return false;
+
+ char pCurPath[ MAX_PATH ];
+ V_strncpy( pCurPath, pFullPathDir, sizeof(pCurPath) );
+ V_StripTrailingSlash( pCurPath );
+ characterset_t breaks;
+ CharacterSetBuild( &breaks, "=\n");
+ do
+ {
+ char pP4ConfigPath[MAX_PATH];
+ V_strncpy( pP4ConfigPath, pCurPath, MAX_PATH );
+ V_strncat( pP4ConfigPath, "\\p4config", MAX_PATH, MAX_PATH );
+ if ( FileExists( pP4ConfigPath ) )
+ {
+ char temp[1024];
+ CUtlBuffer buf( temp, sizeof(temp), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::EXTERNAL_GROWABLE );
+ if ( ReadFile( pP4ConfigPath, NULL, buf ) )
+ {
+ while ( buf.IsValid() )
+ {
+ char token[256], value[256];
+ if ( !buf.ParseToken( &breaks, token, sizeof(token) ) )
+ break;
+ if ( !buf.GetToken("=") )
+ break;
+ if ( !buf.ParseToken( &breaks, value, sizeof(value) ) )
+ break;
+ if ( !V_stricmp(token, "p4client") )
+ {
+ V_strncpy( pClientSpec, value, nMaxLen );
+ return true;
+ }
+ }
+
+ Warning( "Unable to read file %s!\n", pP4ConfigPath );
+ }
+ }
+
+ V_StripLastDir( pCurPath, sizeof(pCurPath) );
+ V_StripTrailingSlash( pCurPath );
+ if ( V_strlen( pCurPath ) <= 2 )
+ break;
+
+ } while (true);
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns which clientspec a file lies under
+//-----------------------------------------------------------------------------
+bool CP4::GetClientSpecForFile( const char *pFullPath, char *pClientSpec, int nMaxLen )
+{
+ // Strip off the file name
+ char pCurPath[MAX_PATH];
+ V_strncpy( pCurPath, pFullPath, sizeof(pCurPath) );
+ V_StripFilename( pCurPath );
+
+ // Recursively search subdirectories until we find a p4config file.
+ return GetClientSpecForDirectory( pCurPath, pClientSpec, nMaxLen );
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns which clientspec a filesystem path ID lies under
+//-----------------------------------------------------------------------------
+bool CP4::GetClientSpecForPath( const char *pPathId, char *pClientSpec, int nMaxLen )
+{
+ if ( !IsConnectedToServer() )
+ {
+ if ( nMaxLen > 0 )
+ {
+ pClientSpec[ 0 ] = '\0';
+ }
+ return false;
+ }
+
+ char pPathBuf[2048];
+#if defined( STANDALONE_VPC )
+ V_strncpy( pPathBuf, ".", V_ARRAYSIZE( pPathBuf ) );
+#else
+ if ( g_pFileSystem->GetSearchPath( pPathId, false, pPathBuf, sizeof(pPathBuf) ) == 0 )
+ return false;
+#endif
+ // FIXME: Would be faster to not check for duplication and just
+ // pick the first client spec it sees. Should I not test the additional paths?
+ bool bFirstPath = true;
+ bool bFoundClientSpec = false;
+ char pTempClientSpec[MAX_PATH];
+ char *pPath = pPathBuf;
+ while ( pPath )
+ {
+ // Find the next path
+ char *pCurPath = pPath;
+ char *pSemi = strchr( pPath, ';' );
+ if ( pSemi )
+ {
+ *pSemi = 0;
+ pPath = pSemi+1;
+ }
+ else
+ {
+ pPath = NULL;
+ }
+
+ // Recursively search subdirectories until we find a p4config file.
+ if ( GetClientSpecForDirectory( pCurPath, pClientSpec, nMaxLen ) )
+ {
+ bFoundClientSpec = true;
+ if ( bFirstPath )
+ {
+ V_strncpy( pTempClientSpec, pClientSpec, sizeof(pTempClientSpec) );
+ }
+ else
+ {
+ // Paths are on different client specs
+ if ( V_stricmp( pClientSpec, pTempClientSpec ) )
+ {
+ pClientSpec[0] = 0;
+ return false;
+ }
+ }
+ }
+ }
+ return bFoundClientSpec;
+}
+
+void CP4::SetOpenFileChangeList( const char *pChangeListName )
+{
+ if ( !m_sChangeListName.Length() ||
+ !pChangeListName || !*pChangeListName ||
+ strcmp( m_sChangeListName.String(), pChangeListName ) ||
+ pChangeListName == m_sChangeListName.String() )
+ {
+ m_sChangeListName.Set( pChangeListName );
+ m_sCachedChangeListNum.Set( "0" );
+ m_nCachedChangeListNumber = 0;
+
+ // Valid changelist
+ {
+ uint32 uiSpewFlag = CErrorHandlerUser::eFilterClUselessSpew;
+ int bAdd = m_sChangeListName.Length();
+ g_ErrorHandlerUser.SetFlags( bAdd ? uiSpewFlag : 0, bAdd ? 0 : uiSpewFlag );
+ }
+ }
+}
+
+char const * CP4::GetOpenFileChangeListNum()
+{
+ if ( m_sChangeListName.Length() )
+ {
+ if ( !m_nCachedChangeListNumber )
+ {
+ m_nCachedChangeListNumber = FindOrCreateChangelist( m_sChangeListName.String() );
+ if ( m_nCachedChangeListNumber )
+ m_sCachedChangeListNum.Format( "%d", m_nCachedChangeListNumber );
+ }
+
+ if ( m_nCachedChangeListNumber )
+ return m_sCachedChangeListNum.String();
+ }
+ return NULL;
+}
+
+static bool MakeFilesWritable( int nCount, const char **ppFullPathList )
+{
+ bool bResult = true;
+
+ // Make sure we can make as many files writable as possible
+ for ( int k = 0; k < nCount; ++ k )
+ {
+ char const *szFile = ppFullPathList[ k ];
+ if ( !access( szFile, 02 ) )
+ continue;
+ if ( !chmod( szFile, _S_IWRITE | _S_IREAD ) )
+ continue;
+ bResult = false;
+ }
+
+ return bResult;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CP4::OpenFileForAdd( const char *fullpath )
+{
+ return PerformPerforceOpCurChangeList( "add", fullpath );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CP4::OpenFileForEdit( const char *fullpath )
+{
+ if ( !PerformPerforceOpCurChangeList( "edit", fullpath ) )
+ return false;
+
+ return MakeFilesWritable( 1, &fullpath );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CP4::OpenFileForDelete( const char *pFullPath )
+{
+ return PerformPerforceOpCurChangeList( "delete", pFullPath );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CP4::SyncFile( const char *pFullPath, int nRevision )
+{
+ char szFileOptions[ MAX_PATH + 128 ];
+
+ if ( nRevision >= 0 )
+ { // sync to a specific revision
+ V_snprintf( szFileOptions, sizeof( szFileOptions ), "%s#%d", pFullPath, nRevision );
+ }
+ else
+ { // sync to the head revision
+ V_snprintf( szFileOptions, sizeof( szFileOptions ), "%s#head", pFullPath );
+ }
+
+ return PerformPerforceOp( "sync", szFileOptions );
+}
+
+
+//-----------------------------------------------------------------------------
+// Submit file
+//-----------------------------------------------------------------------------
+bool CP4::SubmitFile( const char *pFullPath, const char *pDescription )
+{
+ return SubmitFiles( 1, &pFullPath, pDescription );
+}
+
+
+//-----------------------------------------------------------------------------
+// Revert file
+//-----------------------------------------------------------------------------
+bool CP4::RevertFile( const char *pFullPath )
+{
+ return PerformPerforceOp( "revert", pFullPath );
+}
+
+
+//-----------------------------------------------------------------------------
+// Helper for operations on multiple files
+//-----------------------------------------------------------------------------
+bool CP4::PerformPerforceOp( PerforceOp_t op, int nCount, const char **ppFullPathList, const char *pDescription )
+{
+ g_ErrorHandlerUser.ResetErrorState();
+
+ if ( !IsConnectedToServer() )
+ {
+ g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL );
+ return false;
+ }
+
+ if ( nCount == 0 )
+ return true;
+
+ char pOldClientSpec[MAX_PATH];
+ V_strncpy( pOldClientSpec, m_Client.GetClient().Text(), sizeof(pOldClientSpec) );
+
+ char pCurrentClientSpec[MAX_PATH];
+ V_strncpy( pCurrentClientSpec, pOldClientSpec, sizeof(pCurrentClientSpec) );
+
+ int nFirstIndex = 0;
+ while ( nFirstIndex < nCount )
+ {
+ char pClientSpec[MAX_PATH];
+
+ bool bChangeSpec = false;
+ int i;
+ for ( i = nFirstIndex; i < nCount; ++i )
+ {
+ if ( s_p4.GetClientSpecForFile( ppFullPathList[i], pClientSpec, sizeof(pClientSpec) ) )
+ {
+ if ( V_stricmp( pCurrentClientSpec, pClientSpec ) )
+ {
+ bChangeSpec = true;
+ break;
+ }
+ }
+ }
+
+ if ( i != nFirstIndex )
+ {
+ op( i - nFirstIndex, &ppFullPathList[nFirstIndex], pDescription );
+ nFirstIndex = i;
+ }
+
+ if ( bChangeSpec )
+ {
+ s_p4.SetActiveClient( pClientSpec );
+ V_strncpy( pCurrentClientSpec, pClientSpec, sizeof(pCurrentClientSpec) );
+ }
+ }
+
+ if ( V_stricmp( pCurrentClientSpec, pOldClientSpec ) )
+ {
+ s_p4.SetActiveClient( pOldClientSpec );
+ }
+
+ return g_ErrorHandlerUser.GetErrorState() < E_WARN;
+}
+
+static const char *s_pOperation;
+void SimplePerforceOp( int nCount, const char **ppFullPathList, const char *pDescription )
+{
+ s_p4.GetClientApi().SetArgv( nCount, const_cast<char**>( ppFullPathList ) );
+ s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser );
+}
+
+bool CP4::PerformPerforceOp( const char *pOperation, int nCount, const char **ppFullPathList )
+{
+ s_pOperation = pOperation;
+ return PerformPerforceOp( SimplePerforceOp, nCount, ppFullPathList, NULL );
+}
+
+void SimplePerforceOpCurChangeList( int nCount, const char **ppFullPathList, const char *pDescription )
+{
+ if ( char const *szClNum = s_p4.GetOpenFileChangeListNum() )
+ {
+ CUtlVector< const char * > arrArgv;
+ arrArgv.SetCount( nCount + 3 );
+ arrArgv[0] = "-c";
+ arrArgv[1] = szClNum;
+ for ( int k = 0; k < nCount; ++ k )
+ arrArgv[ 2 + k ] = ppFullPathList[ k ];
+ arrArgv[ nCount + 2 ] = NULL;
+
+ s_p4.GetClientApi().SetArgv( nCount + 2, const_cast<char**>( arrArgv.Base() ) );
+ s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser );
+ }
+ else
+ {
+ s_p4.GetClientApi().SetArgv( nCount, const_cast<char**>( ppFullPathList ) );
+ s_p4.GetClientApi().Run( s_pOperation, &g_ErrorHandlerUser );
+ }
+}
+
+bool CP4::PerformPerforceOpCurChangeList( const char *pOperation, int nCount, const char **ppFullPathList )
+{
+ if ( m_sChangeListName.Length() )
+ {
+ s_pOperation = pOperation;
+ return PerformPerforceOp( SimplePerforceOpCurChangeList, nCount, ppFullPathList, NULL );
+ }
+ else
+ {
+ return PerformPerforceOp( pOperation, nCount, ppFullPathList );
+ }
+}
+
+bool CP4::PerformPerforceOp( const char *pOperation, const char *pFullPath )
+{
+ g_ErrorHandlerUser.ResetErrorState();
+
+ if ( !IsConnectedToServer() )
+ {
+ g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL );
+ return false;
+ }
+
+ CScopedFileClientSpec spec( pFullPath );
+
+ char *argv[] = { (char *)pFullPath, NULL };
+ m_Client.SetArgv( 1, argv );
+ m_Client.Run( pOperation, &g_ErrorHandlerUser );
+
+ return g_ErrorHandlerUser.GetErrorState() < E_WARN;
+}
+
+bool CP4::PerformPerforceOpCurChangeList( const char *pOperation, const char *pFullPath )
+{
+ g_ErrorHandlerUser.ResetErrorState();
+
+ if ( !IsConnectedToServer() )
+ {
+ g_ErrorHandlerUser.SetErrorString( "Not connected to P4 server\n", E_FATAL );
+ return false;
+ }
+
+ CScopedFileClientSpec spec( pFullPath );
+
+ char *argv[] = { "-c", "", (char *)pFullPath, NULL }, **pArgv = argv;
+ int numArgv = 3;
+
+ if ( char const *szClNum = GetOpenFileChangeListNum() )
+ {
+ argv[ 1 ] = const_cast< char * >( szClNum );
+ }
+ else
+ {
+ pArgv += 2;
+ numArgv -= 2;
+ }
+
+ m_Client.SetArgv( numArgv, pArgv );
+ m_Client.Run( pOperation, &g_ErrorHandlerUser );
+
+ return g_ErrorHandlerUser.GetErrorState() < E_WARN;
+}
+
+bool CP4::OpenFilesForAdd( int nCount, const char **ppFullPathList )
+{
+ return PerformPerforceOpCurChangeList( "add", nCount, ppFullPathList );
+}
+
+bool CP4::OpenFilesForEdit( int nCount, const char **ppFullPathList )
+{
+ if ( !PerformPerforceOpCurChangeList( "edit", nCount, ppFullPathList ) )
+ return false;
+
+ return MakeFilesWritable( nCount, ppFullPathList );
+}
+
+bool CP4::OpenFilesForDelete( int nCount, const char **ppFullPathList )
+{
+ return PerformPerforceOpCurChangeList( "delete", nCount, ppFullPathList );
+}
+
+bool CP4::RevertFiles( int nCount, const char **ppFullPathList )
+{
+ return PerformPerforceOp( "revert", nCount, ppFullPathList );
+}
+
+
+void SubmitPerforceOp( int nCount, const char **ppFullPathList, const char *pDescription )
+{
+ Assert( nCount > 0 );
+ if ( nCount <= 0 )
+ return;
+
+ // First, create a new changelist
+ g_ChangelistCreateUser.CreateChangelist( pDescription );
+ if ( g_ChangelistCreateUser.GetData().Count() == 0 )
+ {
+ g_ErrorHandlerUser.SetErrorString( "Failed to create changelist for submit operation", E_FATAL );
+ return;
+ }
+
+ int nChangeList = g_ChangelistCreateUser.GetData()[0];
+
+ // Next, move all files to that new changelist
+ char pBuf[128];
+ V_snprintf( pBuf, sizeof(pBuf), "%d", nChangeList );
+ char **ppArgv = (char **)_alloca( (nCount + 2) * sizeof(char*) );
+ ppArgv[0] = "-c";
+ ppArgv[1] = pBuf;
+ memcpy( &ppArgv[2], ppFullPathList, nCount * sizeof(char*) );
+ s_p4.GetClientApi().SetArgv( nCount+2, ppArgv );
+ s_p4.GetClientApi().Run( "reopen", &g_ErrorHandlerUser );
+
+ // Finally, submit that changelist
+ g_SubmitUser.Submit( nChangeList );
+}
+
+bool CP4::SubmitFiles( int nCount, const char **ppFullPathList, const char *pDescription )
+{
+ return PerformPerforceOp( SubmitPerforceOp, nCount, ppFullPathList, pDescription );
+}
+
+
+//-----------------------------------------------------------------------------
+// Opens a file in s_p4 win
+//-----------------------------------------------------------------------------
+void CP4::OpenFileInP4Win( const char *pFullPath )
+{
+ if ( !IsConnectedToServer() )
+ return;
+
+ char pClientSpec[MAX_PATH];
+ char pSystem[512];
+ if ( GetClientSpecForFile( pFullPath, pClientSpec, sizeof(pClientSpec) ) )
+ {
+ V_snprintf( pSystem, sizeof(pSystem), "p4win -q -c %s -s %s", pClientSpec, pFullPath );
+ }
+ else
+ {
+ V_snprintf( pSystem, sizeof(pSystem), "p4win -q -s %s", pFullPath );
+ }
+
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory( &si, sizeof(si) );
+ si.cb = sizeof(si);
+ ZeroMemory( &pi, sizeof(pi) );
+
+ // Start the child process.
+ CreateProcess( NULL, // No module name (use command line).
+ pSystem, // Command line.
+ NULL, // Process handle not inheritable.
+ NULL, // Thread handle not inheritable.
+ FALSE, // Set handle inheritance to FALSE.
+ 0, // No creation flags.
+ NULL, // Use parent's environment block.
+ NULL, // Use parent's starting directory.
+ &si, // Pointer to STARTUPINFO structure.
+ &pi ); // Pointer to PROCESS_INFORMATION structure.
+}
+
+
+//-----------------------------------------------------------------------------
+// Is this file in perforce?
+//-----------------------------------------------------------------------------
+bool CP4::IsFileInPerforce( const char *fullpath )
+{
+ if ( !IsConnectedToServer() )
+ return false;
+
+ CScopedFileClientSpec spec( fullpath );
+
+ g_FileUser.RetrieveFile( fullpath );
+ if ( g_FileUser.GetData().Count() == 0 )
+ return false;
+
+ // Return deleted files as not being in perforce
+ return !g_FileUser.GetData()[0].m_bDeleted;
+}
+
+
+//-----------------------------------------------------------------------------
+// Is this file opened for edit?
+//-----------------------------------------------------------------------------
+P4FileState_t CP4::GetFileState( const char *pFullPath )
+{
+ if ( !IsConnectedToServer() )
+ return P4FILE_UNOPENED;
+
+ CScopedFileClientSpec spec( pFullPath );
+
+ g_FileUser.RetrieveOpenedFiles( pFullPath );
+ if ( g_FileUser.GetData().Count() == 0 )
+ return P4FILE_UNOPENED;
+ return g_FileUser.GetData()[0].m_eOpenState;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns file information for a single file
+//-----------------------------------------------------------------------------
+bool CP4::GetFileInfo( const char *pFullPath, P4File_t *pFileInfo )
+{
+ if ( !IsConnectedToServer() )
+ return false;
+
+ CScopedFileClientSpec spec( pFullPath );
+
+ g_FileUser.RetrieveFile( pFullPath );
+ if ( g_FileUser.GetData().Count() != 1 )
+ return false;
+
+ memcpy( pFileInfo, &g_FileUser.GetData()[0], sizeof(P4File_t) );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Are we connected to the server? (and should we reconnect?)
+//-----------------------------------------------------------------------------
+bool CP4::IsConnectedToServer( bool bRetry )
+{
+ if ( bRetry && m_bConnectedToServer )
+ {
+ // don't comment this back in until it's called less often to avoid spamming the server
+ // (currently, this is called every time the ifm file menu is opened)
+ // this means that we won't detect losing the server until after a failed Run() cmd
+ // -jd
+
+// m_Client.Run( "monitor show" ); // do something to test if the server's still up
+ if ( m_Client.Dropped() )
+ {
+ Shutdown();
+ }
+ }
+
+ if ( !m_bConnectedToServer && bRetry )
+ {
+ Init();
+ }
+
+ return m_bConnectedToServer;
+}
+
+//-----------------------------------------------------------------------------
+// retrieves the last error from the last op (which is likely to span multiple lines)
+// this is only valid after OpenFile[s]For{Add,Edit,Delete} or {Submit,Revert}File[s]
+//-----------------------------------------------------------------------------
+const char *CP4::GetLastError()
+{
+ return g_ErrorHandlerUser.GetErrorString();
+}
diff --git a/external/vpc/p4lib/p4lib.vpc b/external/vpc/p4lib/p4lib.vpc
new file mode 100644
index 0000000..2a846e2
--- /dev/null
+++ b/external/vpc/p4lib/p4lib.vpc
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// P4LIB.VPC
+//
+// Project Script
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR ".."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
+
+$Configuration
+{
+ $Compiler
+ {
+ $AdditionalIncludeDirectories "$BASE,$SRCDIR\common\p4api"
+ }
+
+ $Linker
+ {
+ $AdditionalDependencies "$BASE wsock32.lib"
+ }
+}
+
+$Project "P4lib"
+{
+ $Folder "Source Files"
+ {
+ $File "p4.cpp"
+ }
+
+ $Folder "Header Files"
+ {
+ $File "$SRCDIR/public/p4lib/ip4.h"
+ }
+
+ $Folder "Link Libraries"
+ {
+ $File "$SRCDIR/lib/common/libclient.lib"
+ $File "$SRCDIR/lib/common/librpc.lib"
+ $File "$SRCDIR/lib/common/libsupp.lib"
+ }
+}