# Perfoce VCS back-end code for qct
#
# Copyright 2006 Steve Borho
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
# Usage Notes:
# o Assumes P4CLIENT, P4PORT, P4USER are properly set
# o Will execute out of current directory, but all paths will be listed globally
# o Will always use the default changelist
from qctlib.utils import runProgram
import os
class qctVcsP4:
def initRepo(self, argv):
'''Initialize your revision control system, open repository'''
# Verify we have a valid repository
output = runProgram(['p4', 'fstat', '.'], expectedexits=[0,1])
if output.startswith("Path "):
print output
return -1
else:
return 0
def capabilities(self):
'''Return a list of optional capabilities supported by this VCS'''
# Perforce support is pretty sparse
return ( )
def getLogTemplate(self):
'''Request default log message template from VCS'''
if not os.environ.has_key('P4_LOG_TEMPLATE'):
return ''
logFileName = os.environ['P4_LOG_TEMPLATE']
try:
f = open(logFileName)
text = f.read()
f.close()
return text
except IOError:
return ''
def getAutoSelectTypes(self):
'''Return annotations of file types which should be automatically
selected when a new commit is started'''
return ['A', 'M', 'R']
def generateParentFile(self, workingFile):
'''The GUI needs this file's parent revision in place so the user
can select individual changes for commit (basically a revert)
'''
runProgram(['p4', 'revert', self.clientToDepotMap[workingFile] ])
runProgram(['p4', 'edit', self.clientToDepotMap[workingFile] ])
def dirtyCache(self, fileName):
'''The GUI would like us to forget cached data for this file'''
if self.wdCache.has_key(fileName):
del self.wdCache[fileName]
def scanFiles(self, showIgnored):
'''Request scan for all commitable files from VCS'''
# Called at startup, when 'Refresh' button is pressed, or when
# showIgnored toggled.
fileList = []
self.wdCache = { }
self.clientToDepotMap = { }
# Find open files, this command returns depot paths
statusOutput = runProgram(['p4', 'opened', '...'])
recs = statusOutput.split(os.linesep)
recs.pop() # remove last entry (which is '')
for line in recs:
if line.startswith('... '): break
fileName = line.split('#')[0] # remove rev number and status
results = getP4FileStatus(fileName)
fileList.append(results[2] + ' ' + results[1])
self.clientToDepotMap[results[1]] = results[0]
# Find missing files, this command returns client paths
statusOutput = runProgram(['p4', 'diff', '-sd', '...'])
recs = statusOutput.split(os.linesep)
recs.pop() # remove last entry (which is '')
for fileName in recs:
# current directory may not be revisioned yet
if fileName.startswith('... '): break
results = getP4FileStatus(fileName)
fileList.append('!' + ' ' + results[1])
self.clientToDepotMap[results[1]] = results[0]
return fileList
def getFileStatus(self, itemName):
'''Request file deltas from VCS'''
status = itemName[0]
fileName = itemName[2:]
bFileName = "%" + fileName + " "
if status == 'M':
if self.wdCache.has_key(fileName):
return self.wdCache[fileName]
else:
text = runProgram(['p4', 'diff', '-du', self.clientToDepotMap[fileName] ])
self.wdCache[fileName] = text[:-len(os.linesep)]
return text
elif status == 'A':
return bFileName + "has been added to perforce, but not yet commited"
elif status == 'R':
return bFileName + "has been opened for delete, but has not yet been commited"
elif status == '!':
return bFileName + "was tracked but is now missing, will be removed from perforce if commited"
elif status == '*':
return bFileName + "has no recorded open reason with perforce (what's going on?)"
else:
return "Unknown file type " + status
def commitFiles(self, selectedFileList, logMsgText):
'''Commit selected files'''
# Files in list are annotated (A, M, etc) so this function can
# mark files for add or delete as necessary before instigating the commit.
depotFileNames = []
for f in selectedFileList:
status = f[0]
fileName = f[2:]
if status == '?' or status == 'I':
print "Adding %s to revision control" % fileName
runProgram(['p4', 'add', fileName])
elif status == '!':
print "Removing %s from revision control" % fileName
runProgram(['p4', 'delete', self.clientToDepotMap[fileName] ])
if self.clientToDepotMap.has_key(fileName):
depotFileNames.append(self.clientToDepotMap[fileName])
else:
print "Unable to find depot name of " + fileName
# Perforce doesn't allow you to specify a message on the command line.
# What you have to do is pretend to do the commit and ask for the form it would
# create. You then insert the log message and actual file list into that file and
# then submit that with -i
iform = runProgram(['p4', 'change', '-o'])
recs = iform.split(os.linesep)
oform = ''
for line in recs:
if line == '\t':
logMsgLines = logMsgText.split(os.linesep)
for l in logMsgLines:
oform += '\t' + l + os.linesep
elif line == 'Files:':
oform += line + os.linesep
for i in depotFileNames:
oform += '\t' + i + os.linesep
break
else:
oform += line + os.linesep
# print "Output form that would be submitted to perforce: " + oform
runProgram(['p4', 'submit', '-i'], oform)
print "%d file(s) commited: %s" % (len(selectedFileList), ', '.join(depotFileNames))
def addFiles(self, selectedFileList):
'''Add selected files to version control'''
runProgram(['p4', 'add'] + selectedFileList)
def revertFiles(self, selectedFileList):
'''Revert selected files to last revisioned state'''
revertFileNames = []
for f in selectedFileList:
status = f[0]
fileName = f[2:]
if status == 'R':
print "deleted %s recovered from revision control" % fileName
revertFileNames.append(self.clientToDepotMap[fileName])
elif status == '!':
print "missing %s recovered from revision control" % fileName
runProgram(['p4', 'sync', '-f', self.clientToDepotMap[fileName] ])
elif status == 'A':
print "added %s forgot from revision control" % fileName
revertFileNames.append(self.clientToDepotMap[fileName])
elif status == 'M':
print "modifications to %s reverted" % fileName
revertFileNames.append(self.clientToDepotMap[fileName])
else:
print "File %s not reverted" % fileName
if len(revertFileNames):
runProgram(['p4', 'revert'] + revertFileNames)
else:
print "No revertable files"
def getP4FileStatus(fileName):
'''Helper function which determines a file's open reason'''
fstatOut = runProgram(['p4', 'fstat', fileName])
status = '*' # No open action
# normalize case of pwd and client path, to have a good chance of this actually
# working on windows
cwd = os.path.normcase(os.getcwd())
afterCwd = len(cwd) + 1
recs = fstatOut.split(os.linesep)
for line in recs:
words = line.split(' ')
if len(words) < 2: continue
if words[1] == 'depotFile':
depotName = words[2]
elif words[1] == 'clientFile':
clientName = os.path.normcase(words[2])
# Prune current working directory from client name (make relative)
if clientName.startswith(cwd):
clientName = clientName[afterCwd:]
elif words[1] == 'action':
if words[2] == 'add': status = 'A'
elif words[2] == 'edit': status = 'M'
elif words[2] == 'delete': status = 'R'
return (depotName, clientName, status)
# vim: tw=120