195 lines
5.2 KiB
Python
Executable File
195 lines
5.2 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import os
|
|
import sys
|
|
import getopt
|
|
import marshal
|
|
import logging
|
|
|
|
P4_PORT_AND_USER = ' '
|
|
|
|
def p4( command ):
|
|
"""
|
|
Run a perforce command line instance and marshal the
|
|
result as a list of dictionaries.
|
|
"""
|
|
commandline = 'p4 %s -G %s' % (P4_PORT_AND_USER, command)
|
|
logging.debug( '%s' % commandline )
|
|
stream = os.popen( commandline, 'rb' )
|
|
entries = []
|
|
try:
|
|
while 1:
|
|
entry = marshal.load(stream)
|
|
entries.append(entry)
|
|
except EOFError:
|
|
pass
|
|
code = stream.close()
|
|
if None != code:
|
|
raise IOError( "Failed to execute %s: %d" % (commandline, int(code)) )
|
|
return entries
|
|
|
|
def deleteFile(name):
|
|
"""
|
|
Deletes the file from the repository.
|
|
"""
|
|
p4( 'delete "%s"' % name )
|
|
|
|
def backRevision(name, revision, force):
|
|
"""
|
|
Given a file and a revision number, tries to go back
|
|
one revision in time.
|
|
"""
|
|
targetRevision = revision - 1
|
|
headAction = p4( 'fstat "%s#%d"' % (name, targetRevision))[0]['headAction']
|
|
|
|
if 'delete' == headAction:
|
|
if laterRevisionExists(name, revision) and not force:
|
|
logging.warn("%s has new edits since this change list was submitted. Skipping automated delete; You must delete manually." % (name))
|
|
else:
|
|
deleteFile(name)
|
|
else:
|
|
syncDoActionAndResolve(name, "edit", int(revision), force)
|
|
|
|
def laterRevisionExists(name, revision):
|
|
headRevision = p4( 'fstat "%s"' % (name))[0]['headRev']
|
|
return int(headRevision) > revision
|
|
|
|
|
|
def recoverFile(name, revision, force):
|
|
"""
|
|
Adds a deleted file back to the repository.
|
|
"""
|
|
syncDoActionAndResolve(name, "add", int(revision), force)
|
|
|
|
def syncDoActionAndResolve(name, action, revision, force):
|
|
p4( 'sync "%s#%d"' % (name, revision -1) )
|
|
p4( '%s "%s"' % (action, name) )
|
|
p4( 'sync "%s"' % name )
|
|
|
|
headRevision = int(p4( 'fstat "%s"' % (name))[0]['headRev'])
|
|
logging.debug("revision = %d" % (revision))
|
|
logging.debug("headRevision = %d" % (headRevision))
|
|
|
|
if laterRevisionExists(name, revision) and not force:
|
|
logging.warn("%s has new edits since this change list was submitted. Skipping automated resolve; You must resolve manually." % (name))
|
|
else:
|
|
p4( 'resolve -ay "%s"' % name )
|
|
|
|
|
|
def revertChangelist( changelistNumber, force ):
|
|
"""
|
|
Steps through the whole changelist file by file and looks at
|
|
the last action taken and then tries to go back one step.
|
|
"""
|
|
logging.debug( 'Trying to revert the changelist %d' % changelistNumber )
|
|
description = p4( 'describe -s %d' % changelistNumber )[0]
|
|
infos = []
|
|
counter = 0
|
|
try:
|
|
while 1:
|
|
name = description[ 'depotFile%d' % counter ]
|
|
action = description[ 'action%d' % counter ]
|
|
revision = int(description[ 'rev%d' % counter ])
|
|
infos.append( (name, action, revision) )
|
|
counter += 1
|
|
except KeyError:
|
|
pass
|
|
|
|
for name, action, revision in infos:
|
|
logging.debug( 'Processing %s#%d' % (name, revision) )
|
|
if 'add' == action:
|
|
deleteFile(name)
|
|
if 'edit' == action:
|
|
backRevision(name, revision, force)
|
|
if 'delete' == action:
|
|
recoverFile(name, revision, force)
|
|
if 'branch' == action or 'integrate' == action:
|
|
if 1 == revision:
|
|
deleteFile(name)
|
|
else:
|
|
backRevision(name, revision, force)
|
|
|
|
|
|
def main(argv):
|
|
"""
|
|
Usage: p4revert.py [options] <changelist>
|
|
|
|
Options:
|
|
-v : verbose
|
|
-f : force
|
|
-c client : perforce client
|
|
-p port : perforce port
|
|
-u user : perforce user
|
|
"""
|
|
try:
|
|
options, arguments = getopt.getopt(argv, 'c:p:u:vf')
|
|
except getopt.GetoptError:
|
|
print 'Error parsing arguments'
|
|
print main.__doc__
|
|
return 1
|
|
|
|
# Default tweakable values for the options.
|
|
verbose = False
|
|
force = False
|
|
client = ''
|
|
port = ''
|
|
user = ''
|
|
|
|
# Loop through all the options
|
|
for o,a in options:
|
|
if '-v' == o:
|
|
verbose = True
|
|
if '-c' == o:
|
|
client = a
|
|
if '-p' == o:
|
|
port = a
|
|
if '-u' == o:
|
|
user = a
|
|
if '-f' == o:
|
|
force = True
|
|
|
|
if len(arguments) != 1:
|
|
print 'Must give one changelist number'
|
|
print main.__doc__
|
|
return 1
|
|
|
|
try:
|
|
changelistNumber = int(arguments[0])
|
|
except ValueError:
|
|
print 'Changelist number must be a number!'
|
|
print main.__doc__
|
|
return 1
|
|
|
|
global P4_PORT_AND_USER
|
|
if len(client):
|
|
P4_PORT_AND_USER += ' -c %s ' % client
|
|
if len(port):
|
|
P4_PORT_AND_USER += ' -p %s ' % port
|
|
if len(user):
|
|
P4_PORT_AND_USER += ' -u %s ' % user
|
|
|
|
# At this point we're all done with the options! Now to the real code.
|
|
if verbose:
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format='%(asctime)s %(levelname)-7s: %(message)s' )
|
|
else:
|
|
logging.basicConfig(
|
|
level=logging.INFO, format='%(message)s' )
|
|
|
|
revertChangelist( changelistNumber, force )
|
|
logging.info( 'Revert of %d done.' % changelistNumber )
|
|
results = p4 ( "resolve -n" )
|
|
for result in results:
|
|
code = result['code']
|
|
if code == 'stat':
|
|
logging.warning("%s must be resolved." % (result['fromFile']))
|
|
elif code == 'error':
|
|
logging.info("Change list reverted and files are ready for submit.")
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
# This is just the main stub trick that makes the script act like a regular
|
|
# unix application. We return 1 for errors and 0 for success.
|
|
sys.exit( main(sys.argv[1:]) )
|
|
|