Files
gtav-src/tools_ng/techart/dcc/motionbuilder2014/python/RS/Perforce.py
T
2025-09-29 00:52:08 +02:00

927 lines
23 KiB
Python
Executable File

"""
Description:
Helper functions for interfacing with Perforce through our .NET RSG.SourceControl.Perforce library.
Author:
Jason Hayes <jason.hayes@rockstarsandiego.com>
"""
__DoNotReload__ = True # Module is NOT safe to be dynamciy reloaded
import re
import os
import types
import datetime
import subprocess
import marshal
import platform
import clr
import RS.Config
clr.AddReference("RSG.SourceControl.Perforce")
clr.AddReference("RSG.Base")
from RSG.Base import Logging
from RSG.SourceControl.Perforce import P4
from RSG.SourceControl.Perforce import FileState
from RSG.SourceControl.Perforce import FileAction
from RSG.SourceControl.Perforce import Changelist
from PySide import QtGui
# # Constants # #
# Setup the Perforce connection to use the current project root.
# Wrapped up around try catch as there is a new constructor for p4 coming
try:
p4 = P4()
except:
p4 = P4(Logging.LogFactory.ApplicationLog)
p4.set_MaxLockTime(10)
p4.CWD = RS.Config.Project.Path.Root
try:
import RS.Globals
# Add callback to ensure the connection is dispose on application shut down
RS.Globals.Application.OnFileExit.Add(lambda c, e: p4.Dispose())
except ImportError:
pass
# # Functions # #
def Reconnect():
"""
Disconnects and connects the perforce instance. This is to resolve network issues where
the connection is invalid. This can be called after a long running process to ensure
the connection did not time out.
"""
global p4
p4.Disconnect()
p4.Connect()
def User():
"""
Gets the name of the user using perforce
Return:
string
"""
global p4
return p4.User
def Client():
"""
Gets the name of the client using perforce
Return:
string
"""
global p4
return p4.Client
def Port():
"""
Gets the port used by perforce
Return:
string
"""
global p4
return p4.Port
def Host():
"""
Gets the host for perforce
Return:
string
"""
global p4
return p4.Host
def CreateConfig(directory, client=None, port=None, user=None):
"""
Creates a .p4config file in the supplied directory.
Arguments:
directory (string): The directory to create the .p4config file.
Keyword Arguments:
client (string): The client workspace to set.
port (string): The server and port to use. Format would be: 'rsgsanp4p01:1999'
user (string): The username to use.
"""
if not os.path.isdir(directory):
os.makedirs(directory)
if not port:
port = Port()
if not client:
client = Client()
if not user:
user = User()
try:
p4config = open('{0}\\.p4config'.format(directory), 'w')
p4config.write('P4CLIENT={0}\n'.format(client))
p4config.write('P4PORT={0}\n'.format(port))
p4config.write('P4USER={0}\n'.format(user))
except IOError, err:
print 'Could not create the .p4config file due to the following error!\n', err
finally:
p4config.close()
def SwitchWorkspace(cwd):
"""
Switches the current p4 connection to a new workspace.
Arguments:
cwd (string): Path to the .p4config file containing the client, workspace and server information.
"""
global p4
p4.Disconnect()
p4 = P4()
p4.CWD = cwd
p4.Connect()
def _AssertFileState(fileStateObj):
"""
Asserts if the supplied object isn't of type RSG.SourceControl.Perforce.FileState
Arguments:
fileStateObj (RSG.SourceControl.Perforce.FileState): The RSG.SourceControl.Perforce.FileState object to check.
"""
assert isinstance(fileStateObj, FileState), ('Incorrect type: '
'Must supply a RSG.SourceControl.Perforce.FileState object!')
def DoesFileExist(filename):
"""
Checks to see if the supplied filename exists in Perforce.
Arguments:
filename (string): The filename to check.
Returns:
True or False
"""
return p4.Exists(FixFilePath(filename))
def FixFilePath(filename):
"""
Removes characters not recognized by P4 with their escapes.
Arguments:
filename (string): path to file
Return:
string; path to file with unrecognized characters by perforce removed.
"""
for character, escape, regex in (("%", "%25", "%25|%23|%40"), ("#", "%23", "%23"), ("@", "%40", "%40")):
if not re.search(regex, filename) and character in filename:
filename = filename.replace(character, escape)
# Not sure if this is needed
return str(r"{}".format(filename))
def IsLatest(fileStateObj):
"""
Checks to see if the supplied RSG.SourceControl.Perforce.FileState object is the latest revision.
Arguments:
fileStateObj (RSG.SourceControl.Perforce.FileState): The RSG.SourceControl.Perforce.FileState object to check.
Returns:
True or False
"""
_AssertFileState(fileStateObj)
return fileStateObj.HaveRevision == fileStateObj.HeadRevision
def IsOpenForEdit(fileStateObj):
"""
Checks whether or not the supplied file is opened for edit by the current user.
Arguments:
fileStateObj (RSG.SourceControl.Perforce.FileState): The RSG.SourceControl.Perforce.FileState object to check.
Returns:
True or False
"""
_AssertFileState(fileStateObj)
return fileStateObj.OpenAction == FileAction.Edit
def CanOpenForEdit(fileStateObj):
"""
Tests whether or not a file can be opened for edit by the current user.
Arguments:
fileStateObj (RSG.SourceControl.Perforce.FileState): The RSG.SourceControl.Perforce.FileState object to check.
Returns:
True or False
"""
_AssertFileState(fileStateObj)
return fileStateObj.Locked
# TODO: Create a generic method for calling perforce methods, most of the code making calls to perforce is duplicate
# TODO: code and it doesn't need to be
def FixFilePaths( filenames ):
"""
Some file paths contain @, we need to convert it %
"""
if not isinstance(filenames, (list, tuple)):
filenames = [filenames]
newFilenames = []
for filename in filenames:
filename = FixFilePath(filename)
newFilenames.append( filename )
return newFilenames
def SwitchPathStyle(path):
if os.path.splitdrive(path)[0]:
_, path = os.path.splitdrive(path)[0]
return "/{}{}".format(["", "depot/"]["gta5" in path.lower()], path.replace("\\", "/"))
else:
return r"x:{}".format(path.replace("/", "\\")[1:])
def GetFileState(filenames):
"""
Runs fstat on the supplied filenames.
Arguments:
filenames (list[string, ect.]): The file names to test.
Returns:
A list of RSG.SourceControl.Perforce.FileState objects.
"""
global p4
p4.Connect()
if type(filenames) not in (types.TupleType, types.ListType):
filenames = [filenames]
#Fixing file paths
filenames = FixFilePaths( filenames )
fileStates = list(FileState.Create(p4, filenames))
if len(fileStates) == 1:
return fileStates[0]
return fileStates
def GetFileInfo(localFilePath, revision, key):
"""
Gets information about the file from Perforce based on the revision number and key
Arguments:
localFilePath (string): local perforce file path to test
revision (int): revision you want to check
key (string): the type of information you wish to return; acceptable values are
time, path, status, desc, client, change, changeType, and user.
Returns:
key result - could be int/string etc.
"""
global p4
p4.Connect()
result = None
filePathState = GetFileState(localFilePath)
if filePathState:
depotFile = filePathState.DepotFilename
pathWithRevision = [str('{0}#{1}').format(str(depotFile), str(revision))]
recordSet = p4.Run('changes', pathWithRevision)
if recordSet.Records.Length != 0 and recordSet.Records[0].Fields.ContainsKey(key):
result = recordSet.Records[0].Fields.get_Item(key)
return result
def GetAllMarkedDeleteDepotFiles(startingDirectory):
"""
Gets ALL the files marked for delete within the supplied directory. This includes fi
Arguments:
startingDirectory (string): directory to inspect
lResourceDirectory = "//depot/gta5/art/animation/resources/...fbx"
Returns:
list[string, etc.]; A list of depot file names.
"""
global p4
cmd = "opened"
args = ["-a", startingDirectory]
p4Files = p4.Run(cmd, args)
fileList = [record[key]
for record in p4Files.Records
for key in record.get_Fields().Keys
if key == "depotFile" and record['action'] == 'delete']
return fileList
def Add(filenames, changelistNum=None, force=False, fileType=None):
"""
Adds the supplied filenames to Perforce.
Arguments:
filenames (string): The filenames to add.
Keyword Arguments:
changelistNum (int):
Add files to the specified changelist.
force (boolean):
Indicates that the file should be force added
fileType (string):
The file type that we want to add the file as. Should be string that matches
one of the following list of fle types;
http://www.perforce.com/perforce/r15.1/manuals/cmdref/file.types.html
Returns:
A single or list of RSG.SourceControl.Perforce.FileState objects that were added.
"""
global p4
p4.Connect()
if type(filenames) not in (types.TupleType, types.ListType):
filenames = [filenames]
# IMPORTANT: p4 add should NOT escape symbols in the file paths (@, etc).
# The P4 Add command accepts characters normally ignored by the other P4 commands.
# Replacing the characters with their escapes would add a file to perforce with the escapes in
# the filename instead of the intended character.
args = [filename for filename in filenames]
if fileType:
args[0:0] = ['-t', fileType]
if force:
args[0:0] = ['-f']
if changelistNum:
args[0:0] = ['-c', str(changelistNum)]
recordSet = p4.Run('add', args)
fileStates = list(FileState.Create(p4, recordSet))
if len(fileStates) == 1:
return fileStates[0]
return fileStates
def Edit(filenames, changelistNum=None, fileType=None):
"""
Opens for edit the supplied filenames.
Arguments:
filenames (list[string, etc.]): The filenames to open for edit.
Keyword Arguments:
changelistNum (int):
Open for edit the files under this changelist.
fileType:
The file type that we want to add the file as. Should be string that matches
one of the following list of fle types;
http://www.perforce.com/perforce/r15.1/manuals/cmdref/file.types.html
Returns:
A single or list of RSG.SourceControl.Perforce.FileState objects that were opened for edit.
"""
global p4
p4.Connect()
if type(filenames) not in (types.TupleType, types.ListType):
filenames = [filenames]
filenames = FixFilePaths(filenames)
# Get the filenames
args = [filename for filename in filenames]
# Add file type flag to the front of the args list
if fileType:
args[0:0] = ['-t', fileType]
# Add changelist flag to the front of the args list
if changelistNum:
args[0:0] = ['-c', str(changelistNum)]
recordSet = p4.Run('edit', args)
fileStates = list(FileState.Create(p4, recordSet))
if len(fileStates) == 1:
return fileStates[0]
return fileStates
def Sync(filenames, force=False, revision=None):
"""
Syncs the supplied filename(s).
Arguments:
filenames (list[string, etc.]): The filename(s) to sync.
Keyword Arguments:
force (boolean): Whether or not to force the sync.
Returns:
A single or list of RSG.SourceControl.Perforce.FileState objects that were synced.
"""
global p4
p4.Connect()
if type(filenames) not in (types.TupleType, types.ListType):
filenames = [filenames]
filenames = FixFilePaths(filenames)
args = []
if force:
args.append('-f')
for filename in filenames:
if revision:
filename = '{}#{}'.format(filename, revision)
args.append(filename)
print args
recordSet = p4.Run('sync', args)
fileStates = list(FileState.Create(p4, recordSet))
if len(fileStates) == 1:
return fileStates[0]
return fileStates
def GetChangelist(changelistNum):
"""
Query a changelist.
Arguments:
changelistNum(int): The changelist number to query.
Returns:
RSG.SourceControl.Perforce.Changelist object. None if not found.
"""
global p4
p4.Connect()
changelist = Changelist.Create(p4, [changelistNum])
if changelist.Length == 1:
return changelist[0]
return None
def CreateChangelist(description):
"""
Creates a new changelist.
Arguments:
description (string): The changelist desription.
Returns:
RSG.SourceControl.Perforce.Changelist object.
"""
global p4
p4.Connect()
changelist = p4.CreatePendingChangelist(description)
return GetChangelist(changelist.Number)
def GetFilesInChangelist(changelistNum):
"""
Query all files in a changelist.
Arguments:
changelistNum (int): The changelist number to query.
Returns:
List of files found in the changelist.
"""
changelist = GetChangelist(changelistNum)
if changelist:
return list(changelist.Files)
return None
def GetDescription(changelistNumber):
"""
Gets the description from the given changelist. The results are returned in a normal python dictionary
Arguments:
changelistNumber (int): changelist number
Returns:
dictionary
"""
results = p4.Run("describe", [str(changelistNumber)])
return ConvertRecordSetToDictionary(results)
def GetUserData(user):
"""
Gets information on the user based on the perforce user name. The results are returned in a normal python dictionary
Arguments:
user (string): perforce username to get data from
Return:
dictionary
"""
results = p4.Run("users", [user])
return ConvertRecordSetToDictionary(results)
def ConvertRecordSetToDictionary(recordSet):
return {key: record[key] for record in recordSet.Records for key in record.Fields.Keys}
def DeleteChangelist(changelistNum, deleteShelvedFiles=True):
"""
Delete a changelist.
Arguments:
changelistNum(int): The changelist number to delete.
Keyword Arguments:
deleteShelvedFiles(boolean): Delete shelved files in the changelist.
Returns:
P4API.P4RecordSet object.
"""
global p4
p4.Connect()
if deleteShelvedFiles:
DeleteShelvedFiles(changelistNum)
return p4.Run('change', ['-d', str(changelistNum)])
def RevertChangelist(changelistNum, deleteShelvedFiles=False):
"""
Revert all files in a changelist.
Arguments:
changelistNum (int): The changelist number to delete.
Keyword Arguments:
deleteShelvedFiles (boolean): Delete any shelved files in the changelist.
Returns:
P4API.P4RecordSet object.
"""
global p4
p4.Connect()
if deleteShelvedFiles:
DeleteShelvedFiles(changelistNum)
return p4.Run('revert', ['-c', str(changelistNum), '//...'])
def DeleteShelvedFiles(changelistNum):
"""
Delete all shelved files in a changelist.
Arguments:
changelistNum (int): The changelist number to delete.
Returns:
P4API.P4RecordSet object.
"""
global p4
p4.Connect()
return p4.Run('shelve', ['-c', str(changelistNum), '-d'])
def ShelveChangelist(changelistNum):
"""
Shelve all files in a changelist.
Arguments:
changelistNum (int): The changelist number.
Returns:
P4API.P4RecordSet object.
"""
global p4
p4.Connect()
return p4.Run('shelve', ['-c', str(changelistNum), '-f'])
def Shelve(filenames, changelistNum=None):
"""
Shelve a list of files.
Arguments:
filenames (list[string, etc.]): The filenames to shelve.
Keyword Arguments:
changelistNum (int): The changelist number.
Returns:
P4API.P4RecordSet object.
"""
global p4
p4.Connect()
if type(filenames) not in (types.TupleType, types.ListType):
filenames = [filenames]
args = [filename for filename in filenames]
if changelistNum:
args[0:0] = ['-c', str(changelistNum), '-f']
return p4.Run('shelve', args)
def Revert(filenames):
"""
Revert a list of files.
Arguments:
filenames (list[string, etc.]): The filenames to revert.
Returns:
P4API.P4RecordSet object.
"""
global p4
p4.Connect()
if type(filenames) not in (types.TupleType, types.ListType):
filenames = [filenames]
filenames = FixFilePaths(filenames)
args = [filename for filename in filenames]
return p4.Run('revert', args)
def Submit(changelistNum):
"""
Submit a changelist.
Arguments:
changelistNum (int): The changelist number to submit.
Returns:
P4API.P4RecordSet object.
"""
global p4
p4.Connect()
return p4.Run('submit', ['-c', str(changelistNum)])
def Run(p4Cmd, args=None):
"""
Native P4 Run command.
Arguments:
p4Cmd (string): valid p4 cmd
args (list[strings, etc.]): list of paths and/or perforce arguments
Returns:
P4 Record or error message
"""
global p4
p4.Connect()
records = p4.Run(p4Cmd, False, args)
if records.HasErrors():
# This shouldn't be here, but to stop other tools from breaking, this is a lesser of 2 evils.
# Would be good to re-writing this as module a class object
QtGui.QMessageBox.critical(None, "P4 Error", "P4 the following errors:\n{0}".format(records.ErrorMessage))
return records
else:
return records
def ConvertPerforceSecondsToDate(seconds):
"""
Converts the seconds returned by perforce to a date and time
Arguments:
seconds (int): number of seconds grabbed from the perforce server
Return:
string
"""
return str(datetime.datetime(1970, 1, 1) + datetime.timedelta(0, int(seconds)))
def getHostClients( server ):
"""
This will return the p4 hosts client name. There is a possibility a machine can have on or more clients so will return a list
:return: List of Strings
"""
# Using the global G option so we get back a python dictionary which is easier to work with
cmd = 'p4 -G -p {0} clients -E *{1}*'.format(server, platform.node())
proc = subprocess.Popen( cmd, stdout=subprocess.PIPE , shell=True)
# because we may have multiple results we need to append them to a list
clients = []
try:
while 1:
output = marshal.load(proc.stdout)
client = output.get('client',None)
if client != None:
clients.append(client)
except EOFError:
pass
finally:
proc.stdout.close()
return clients
def SwitchToMocapWorkspace(server="rsglicp4s1:1666"):
"""
Currently the p4 api doesn't allow the parsing of global parameters- before the actual command
This is currently the only way for us to gain access to the mocap server via script
"""
global p4
p4.Connect()
# First check to see if we have already created a config file for the mocap server
mocapServerConfig = os.path.normpath("X:\\projects\.p4config")
if os.path.exists(mocapServerConfig):
SwitchWorkspace(mocapServerConfig)
else:
# If we don't create one
# This is a more robust way to get the valid user clients.
clients = getHostClients(server)
if len(clients) > 0:
# Create config file, we will assume to use the first client we find, should be rare to have multiple
# clients
CreateConfig("X:\\projects", client=clients[len(clients)-1], port= server, user=User())
# Switch to the mocap server
SwitchWorkspace(mocapServerConfig)
def Connected():
""" Connects to perforce """
global p4
return p4.IsValidConnection(True, True)
class SyncOperation(object):
"""
Perforce p4.exe command line
"""
def __init__(self, fileList, start= False, force= False):
"""
Contructor
"""
self.p4root = RS.Config.Project.Path.Root
self.fileList = FixFilePaths(fileList)
self.force = force
self.proc = None
if start:
self.Start()
def Start(self):
"""
Sync Operation
"""
# send a request for a sync to perforce
fileListString = ""
for i in range(len(self.fileList)):
fileListString= fileListString+ ("\"{0}\" ").format(self.fileList[i])
if self.force:
commandText = ('p4 sync -f {0} ').format(fileListString)
else:
commandText = ('p4 sync {0} ').format(fileListString)
# Remember the original working directory
originaDir = RS.Config.Project.Path.Root
# Change it to the current working one so that we can use the p4 command
os.chdir( self.p4root )
try:
self.proc = subprocess.Popen(commandText, shell=True, stdout=subprocess.PIPE)
except:
self.proc = subprocess.Popen(commandText, shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
self.proc.stdin.close()
self.proc.stderr.close()
# Change it back to the original directory
os.chdir( originaDir )
def WaitForCompletion(self):
"""
Wait for process to finish
"""
self.proc.wait()