# P4Utilities.py # David Bowman # September 19, 2001 # # Utility functions for using Perforce. import sys, re, os, marshal, string import time import MailUtilities if sys.platform == 'win32': gDictFlags = 'rb' else: gDictFlags = 'r' # on MacOS X, Perforce 2002.1 has a bug which causes it to either crash or corrupt the # operating system unless it is called by its full path name (UGLY) if sys.platform in ['darwin1', 'darwin']: p4 = '/usr/bin/p4 ' else: p4 = 'p4 ' gMailSender = 'EngineeringReports' gMailPrefix = 'CallP4:' def SendMail(recipients, subject, message): MailUtilities.SendMail(MailUtilities.MakeLocalAddress(gMailSender), recipients, gMailPrefix + ' ' + subject, message, []) gScriptAdministratorEmail = [('Paul Pharr', MailUtilities.MakeLocalAddress('pharr'))] def CheckError(errorFile, timeSinceInitialTry): retryInterval = 60 # this exposes us to a serious deadlock condition if the standard out # stream contains a lot of data, so I've disabled error checking for now - PCP errorLine = errorFile.read() errorFile.close() if errorLine != "": sys.stderr.write("CallP4 experienced transient error: %s\n" % (errorLine)) # be careful with this match as some of the error files are opened as # binary and some are opened as text. We can't rely on CR/LFs behaving here - PCP m = re.match(r'^Perforce client error.*', errorLine) if m: sys.stderr.write("p4 is not responding - will retry in %d sec.\n" % (retryInterval)) # Send a message every hour that P4 is down # later we'll silence this for outages less than a certain minimum if timeSinceInitialTry % 3600 < retryInterval: SendMail(gScriptAdministratorEmail, "Perforce is down", "Perforce has not been responding for %6.1f minutes.\n\n" % (timeSinceInitialTry / 60.0)) time.sleep(retryInterval) return 1 else: if timeSinceInitialTry > 0: SendMail(gScriptAdministratorEmail, "Perforce is back up", "Perforce is back up after being down for %6.1f minutes.\n\n" % (timeSinceInitialTry / 60.0)) return 0 kReturnFile = 0 kReturnDict = 1 kReturnDictList = 2 kReturnFileWithErrors = 3 def CallP4(cmd, returnKind=kReturnFile, client='', user='', password='', host='', port='', retryOnError=0): ''' Use this to call Perforce with your command line. Do not start your command with "p4". Parameters: cmd : The command line to run. returnKind : Set to 1 to return a marshaled dictionary. The "-G" flag will be added for you. openFlags : File open flags such as 'r' or 'rb'. Returns: Either the opened file or the dictionary. ''' options = '' if client != '': options += ' -c "' + client +'"' if user != '': options += ' -u "' + user +'"' if password != '': options += ' -P "' + password +'"' if host != '': options += ' -H "' + host +'"' if port != '': options += ' -p "' + port +'"' timeSinceInitialTry = 0 startTime = int(time.time()) while 1: # before executing every command, we execute a simple commmand which doesn't return much data so we can catch the error # generated if the Perforce server is down. If this command succeeds, then we are comfortable proceeding to call the real # command we want to execute. This is necessary because of the deadlock situation created if we try to read the error result # of a command which returns a lot of data - PCP inFile, f, errors = os.popen3(p4 + options + " counter change", 't') inFile.close() if retryOnError and CheckError(errors, timeSinceInitialTry): timeSinceInitialTry = int(time.time()) - startTime continue if returnKind == kReturnDict: inFile, f, errors = os.popen3(p4 + options + ' -G ' + cmd, 'b') inFile.close() # if retryOnError and CheckError(errors, timeSinceInitialTry): # timeSinceInitialTry = int(time.time()) - startTime # continue return marshal.load(f) elif returnKind == kReturnFile: inFile, f, errors = os.popen3(p4 + options + " " + cmd, 't') inFile.close() # if retryOnError and CheckError(errors, timeSinceInitialTry): # timeSinceInitialTry = int(time.time()) - startTime # continue return f elif returnKind == kReturnFileWithErrors: if retryOnError: raise "CallP4 does not allow retryOnError with kReturnFileWithErrors" # for this case, openFlags must be either t or b inFile, outAndErrFile = os.popen4(p4 + options + " " + cmd, 't') inFile.close() return outAndErrFile elif returnKind == kReturnDictList: dictList = [] inFile, f, errors = os.popen3(p4 + options + ' -G ' + cmd, 'b') inFile.close() # if retryOnError and CheckError(errors, timeSinceInitialTry): # timeSinceInitialTry = int(time.time()) - startTime # continue while 1: try: opened_dict = marshal.load(f) dictList.append(opened_dict) except EOFError: break return dictList else: raise "CallP4 does not understand this returnKind constant" def DoesUserHaveCurrentRevision(file, client='', user='', returnRevisions=0, retryOnError=0): ''' Determine if a user has the most current version of a file. Parameters: file : The full path to the depot file to check. client : The client spec to use. Will use client if not supplied. user : The user to check. Will use current user if not supplied. returnRevisions : If 0, will only return 'boolean', if non-0, will return the 'boolean, have, need'. (see below) Returns: (boolean, have, need) boolean: 0 : User does not have the most current version. 1 : User has the most current version. have: The reviison of the file that the user has. need: The head revision number of the file. ''' haveCurrent = 0 haveRev = 0 needRev = GetCurrentFileRevision(file) if needRev > 0: haveRev = GetUserFileRevision(file, client, user, retryOnError=retryOnError) if haveRev > 0: haveCurrent = haveRev == needRev if returnRevisions: return haveCurrent, haveRev, needRev return haveCurrent def GetChangelistAuthor(cl, retryOnError=0): ''' Get the username of the person who submitted a changelist. Parameters: cl : Checgelist number to check. ''' changelistDescDict = CallP4('describe -s ' + str(cl), kReturnDict, retryOnError=retryOnError) username = changelistDescDict['user'] return username def GetChangelistClient(cl, retryOnError=0): ''' Get the clientspec used for a changelist. Parameters: cl : Changelist number to check. ''' dict = CallP4('change -o ' + str(cl), kReturnDict, retryOnError=retryOnError) return dict['Client'] def GetChangelistDescription(cl, retryOnError=0): ''' Get the text description of the changelist number passed to this function. Parameters: cl : Changelist number to check. ''' dict = CallP4('change -o ' + str(cl), kReturnDict, retryOnError=retryOnError) return dict['Description'] def GetCounter(name, retryOnError=0): cmd = "counter " + name dict = CallP4(cmd, kReturnDict, retryOnError=retryOnError) return int( dict['data'] ) def GetClientspecViews(cs, retryOnError=0): dict = CallP4("client -o " + cs, kReturnDict, retryOnError=retryOnError) views = []; i = 0 while 1: try: v = dict['View' + str(i)] v = v.replace('"', '') # remove any quotes t = v.split('/... ') # split into depot path and local path t[0] += '/...' # put back the chars the split removed views.append(t) i += 1 except: break return views def SetChangelistDescription(cl, description, retryOnError=0): ''' Set the text description of the changelist number passed to this function. Parameters: cl : Changelist number to modify. description : new description for this changelist ''' dict = CallP4("change -o " + str(cl), kReturnDict, retryOnError=retryOnError) # this function demonstrates the technique of passing a python # object through stdin to a Perforce command # note that we don't retry on error here. It's not often a problem as we're guaranteed # to have just successfully talked to the server in th CallP4 above - PCP (inFile, outFile) = os.popen2(p4 + " -G change -i -f", 'b') dict['Description'] = description marshal.dump(dict, inFile) inFile.close() def SetCounter(name, value, retryOnError=0): cmd = "counter " + name + " " + str(value) CallP4(cmd, retryOnError=retryOnError) def GetChangelistFiles(cl, retryOnError=0): ''' Get the files in a changelist. Parameters: cl : Changelist to check. Returns: A string array of all the files in the changelist. If the fuction failes, the array will be empty. ''' files = [] try: changeDict = CallP4('change -o ' + str(cl), kReturnDict, retryOnError=retryOnError) if changeDict['Status'] == 'pending': i = 0 while 1: try: fn = changeDict['Files' + str(i)] files.append(fn) i += 1 except: break elif changeDict['Status'] == 'submitted': describeDict = CallP4('describe -s ' + str(cl), kReturnDict, retryOnError=retryOnError) i = 0 while 1: try: fn = describeDict['depotFile' + str(i)] files.append(fn) i += 1 except: break except: return files return files def GetCurrentFileRevision(file, retryOnError=0): ''' Get the revision number of the most current version of a file. ''' cmd = 'files "' + file + '"' dict = CallP4(cmd, kReturnDict, retryOnError=retryOnError) return int(dict['rev']) def GetLatestFileRevisionDate(file, retryOnError=0): ''' Get the date of the submit of the most recent revision of a file. ''' cmd = 'filelog -m 1 "' + file + '"' dict = CallP4(cmd, kReturnDict, retryOnError=retryOnError) try: datetime = int(dict['time0']) except: datetime = 0 return datetime def GetUserFileRevision(file, client='', user='', retryOnError=0): ''' Get the current revision number of a file that the user has in their client. This function will fail if the client spec has a host restriction. Returns: -1 : An error occurred or the user does not have any revision of the file. >0 : The revision of the file that the user has. ''' cmd = '' if user: cmd += '-u ' + user + ' ' if client: cmd += '-c ' + client + ' ' cmd += 'have "' + file + '"' dict = CallP4(cmd, kReturnDict, retryOnError=retryOnError) g = re.match(r'^.+#(\d+) ', dict['data']) if g: rev = int(g.groups()[0]) return rev return -1 def IsUserInGroup(user, group, retryOnError=0): ''' Determine if a given user is part of the given group. ''' user = string.lower(user) dict = CallP4('group -o ' + group, kReturnDict, retryOnError=retryOnError) i = 0 try: while dict['Users' + str(i)]: u = dict['Users' + str(i)] u = string.lower(u) if u == user: return 1 i += 1 except: return 0 return 0 def OpenForEdit(fn, changelist=0, retryOnError=0): ''' Open a file for edit. Parameters: fn : The depot file to open. changelist : The changelist into which the file will be opened. If 0, the default changelist will be used. Returns: 0 : Failure. 1 : Sucess. ''' if changelist == 0: cmd = 'edit "' + fn + '"' else: cmd = 'edit -c ' + str(changelist) + ' "' + fn + '"' dict = CallP4(cmd, kReturnDict, retryOnError=retryOnError) try: if dict['code'] == 'error': return 0 except: return 1 return 1 def Submit(changeList, retryOnError=0): dict = CallP4('submit -c ' + str(changeList), kReturnDict, retryOnError=retryOnError) try: if dict['code'] == 'error': return 0 except: return 1 return 1 def UserLookup(who, retryOnError=0): ''' Gievn a Perforce username, lookup their email address and full name. ''' userDescDict = CallP4('user -o ' + who, kReturnDict, retryOnError=retryOnError) email = userDescDict['Email'] fullname = userDescDict['FullName'] return email, fullname