#!/usr/bin/env python3.3
'''
/*
* Copyright (c) 2016, Charles McLouth
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL STEWART LORD BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
$Id: //guest/cmclouth/python-utils/p4submit/src/p4submit.py#8 $
*/
'''
import logging
import sys
import time
import argparse
import P4
logger = logging.getLogger(__name__)
RETRY_COUNT = 5
class P4SubmitError(Exception):
def __str__(self):
if len(self.args) == 1:
if isinstance(self.args[0], P4.P4Exception):
return self.args[0].__str__()
return super(P4SubmitError, self).__str__()
def processInputParams(pargs=sys.argv):
logger.debug('enter')
gParser = argparse.ArgumentParser()
gParser.description="p4submit tool"
gParser.epilog= "p4submit tool"
optsGroup = gParser.add_argument_group('g-opts - Global Options')
optsGroup.add_argument('-c', dest='p4client', metavar='client', \
help='Overrides any P4CLIENT setting with the specified client name.')
optsGroup.add_argument('-d', dest='p4dir', metavar='dir', \
help='Overrides any PWD setting (i.e. current working directory) and replaces'+\
' it with the specified directory.')
optsGroup.add_argument('-H', dest='p4host', metavar='host', \
help='Overrides any P4HOST setting with the specified hostname.')
optsGroup.add_argument('-p', dest='p4port', metavar='port', \
help='Overrides any P4PORT setting with the specified port number.')
optsGroup.add_argument('-P', dest='p4passwd', metavar='password', \
help='Overrides any P4PASSWD setting with the specified password.')
optsGroup.add_argument('-u', dest='p4user', metavar='user', \
help='Overrides any P4USER, USER, or USERNAME setting with the specified user name.')
optsGroup.add_argument('-C', dest='p4charset', metavar='charset', \
help='Overrides any P4CHARSET setting with the specified character set.')
optsGroup.add_argument('-F', dest='forceLogin', action='store_true', \
help='Force a login for the supplied super user.')
optsGroup.add_argument('-l', dest='logfile', metavar='logfile', \
help='enable debug logging to a logfile.')
subParsers = gParser.add_subparsers(title='Commands', help='-h, --help additional help')
sParser = subParsers.add_parser('submit')
sParser.set_defaults(func=submitShelf)
sParser.description="submit -- submit shelf."
sParser.add_argument('-t', dest='token', metavar='token', \
help='token to optionally filter upon.')
sParser.add_argument('shelvedChange', metavar='shelvedChange#', type=int, nargs=1, \
help='shelved changelist.')
args = gParser.parse_args(pargs)
if args.logfile is not None and len(args.logfile) > 0:
logger.setLevel(logging.DEBUG)
debugHandler = logging.FileHandler(args.logfile, 'a')
debugHandler.setFormatter(logging.Formatter('%(levelname)s:%(filename)s:%(lineno)d:%(funcName)s:%(message)s'))
logger.addHandler(debugHandler)
logger.debug(pargs)
logger.debug(args)
logger.debug('exit')
return args
def getP4Connection(args):
logger.debug('enter')
logger.debug(args)
p4api = P4.P4()
if isinstance(args, argparse.Namespace):
if args.p4charset is not None: p4api.charset = args.p4charset
if args.p4client is not None: p4api.client = args.p4client
if args.p4dir is not None: p4api.cwd = args.p4dir
if args.p4host is not None: p4api.host = args.p4host
if args.p4passwd is not None: p4api.password = args.p4passwd
if args.p4port is not None: p4api.port = args.p4port
if args.p4user is not None: p4api.user = args.p4user
elif isinstance(args, dict):
var = 'P4CHARSET'
val = args.get(var)
if val is not None and len(val) > 0: p4api.charset = val
var = 'P4CLIENT'
val = args.get(var)
if val is not None and len(val) > 0: p4api.client = val
var = 'P4DIR'
val = args.get(var)
if val is not None and len(val) > 0: p4api.cwd = val
var = 'P4HOST'
val = args.get(var)
if val is not None and len(val) > 0: p4api.host = val
var = 'P4PASSWORD'
val = args.get(var)
if val is not None and len(val) > 0: p4api.password = val
var = 'P4PORT'
val = args.get(var)
if val is not None and len(val) > 0: p4api.port = val
var = 'P4USER'
val = args.get(var)
if val is not None and len(val) > 0: p4api.user = val
p4api.prog = 'p4submit'
p4api.connect()
logger.debug('exit')
return p4api
def validateArgs(args):
logger.debug('enter')
p4SuperAPI = getP4Connection(args)
if args.forceLogin: p4SuperAPI.run_login()
# A shelve followed by a revert is two separate commands, therefore we will
# retry our describe up to RETRY_COUNT times awaiting no open files or give
# up on the entire process.
for retryCount in range(1,RETRY_COUNT):
p4result = p4SuperAPI.run_describe(args.shelvedChange[0])
describeDict = p4result[0]
logger.debug('run_describe(%d)=%s' % (args.shelvedChange[0], p4result))
if describeDict is not None:
if 'shelved' not in describeDict:
if p4SuperAPI.connected(): p4SuperAPI.disconnect()
raise P4SubmitError("%s - no such shelved changelist." % args.shelvedChange[0])
if args.token is not None and len(args.token) > 0:
# check the description
# TODO: should this raise an exception?
if describeDict['desc'].find(args.token) < 0:
describeDict = None
break
if 'depotFile' not in p4result[0] or len(p4result[0]['depotFile']) < 1:
break
else:
time.sleep(retryCount)
if 'depotFile' in p4result[0] and len(p4result[0]['depotFile']) > 0:
describeDict = None
logger.debug('describeDict: %s' % (describeDict))
logger.debug('exit')
return (p4SuperAPI, describeDict)
def submitShelf(args):
logger.debug('enter')
retValue = None
caughtException = None
try:
p4SuperAPI = None
describeDict = None
state = 0
(p4SuperAPI, describeDict) = validateArgs(args)
state += 1
if describeDict is None:
logger.debug('ignoring changelist %s' % args.shelvedChange[0])
else:
changeOwner = describeDict['user']
# impersonate the change owner
if changeOwner != p4SuperAPI.user:
p4loginResult = p4SuperAPI.run_login(changeOwner)
logger.debug('p4SuperAPI.run_login(%s)=%s' % (changeOwner, p4loginResult))
state += 1
tempClientName = '%s_%d' % (p4SuperAPI.client, args.shelvedChange[0])
p4UserAPI = getP4Connection({'P4CHARSET': p4SuperAPI.charset,
'P4CLIENT': tempClientName,
'P4DIR': p4SuperAPI.cwd,
'P4HOST': p4SuperAPI.host,
'P4PORT': p4SuperAPI.port,
'P4USER': changeOwner})
state += 1
if logger.isEnabledFor(logging.DEBUG):
logger.debug('fetch_user(%s)=%s' % (changeOwner, p4UserAPI.fetch_user()))
# create a temporary client
changeClientSpec = p4SuperAPI.fetch_client(describeDict['client'])
logger.debug('p4SuperAPI.fetch_client(%s)=%s' % (describeDict['client'], changeClientSpec))
if 'Stream' in changeClientSpec:
tempClientSpec = p4SuperAPI.fetch_client('-S', changeClientSpec['Stream'], tempClientName)
else:
tempClientSpec = p4SuperAPI.fetch_client('-t', changeClientSpec['Client'], tempClientName)
p4saveclientResult = p4SuperAPI.save_client(tempClientSpec)
state += 1
p4submitResult = p4UserAPI.run_submit('-e', args.shelvedChange[0])
logger.debug('run_submit(-e, %d)=%s' % (args.shelvedChange[0], p4submitResult))
state += 1
submittedChange = None
for row in p4submitResult:
if 'submittedChange' in row:
submittedChange = row['submittedChange']
break
retValue = p4SuperAPI.run_describe(submittedChange)
logger.debug('run_describe(%s)=%s' % (submittedChange, retValue))
except Exception as E:
caughtException = E
logger.debug('Exception Caught: %s' % E)
finally:
if state >= 4 and p4SuperAPI is not None:
logger.debug('cleanup client %s' % tempClientName)
p4SuperAPI.delete_client(tempClientName)
if state >= 3 and p4UserAPI is not None:
logger.debug('cleanup user connection %s' % p4UserAPI.user)
p4UserAPI.disconnect()
if state >= 2 and p4SuperAPI is not None and changeOwner != p4SuperAPI.user:
logger.debug('logout user %s' % changeOwner)
p4SuperAPI.run_logout(changeOwner)
if state >= 1 and p4SuperAPI is not None:
logger.debug('cleanup super user connection %s' % p4SuperAPI.user)
if args.forceLogin: p4SuperAPI.run_logout()
if p4SuperAPI.connected(): p4SuperAPI.disconnect()
if caughtException is not None: raise caughtException
logger.debug('exit - retValue:%s' % retValue)
return retValue
def formatResults(describeRecord):
logger.debug('enter')
if describeRecord is not None:
sResult = "Change %s by %s@%s on %s\n\n\t\t%s\n\nAffected files ...\n\n" % \
(describeRecord[0]['change'], describeRecord[0]['user'], describeRecord[0]['client'], \
str(describeRecord[0]['time']), describeRecord[0]['desc'][0:50])
for x in range(0,len(describeRecord[0]['depotFile'])):
sResult = sResult + "... %s#%s %s\n" % \
(describeRecord[0]['depotFile'][x], describeRecord[0]['rev'][x], \
describeRecord[0]['action'][x])
logger.debug('exit')
else: sResult = ''
return sResult
if __name__ == '__main__':
args = processInputParams(sys.argv[1:])
if hasattr(args, 'func'):
try:
result = args.func(args)
print(formatResults(result))
except P4.P4Exception as e:
logger.debug(str(e))
if len(e.errors) < 1: print(e.warnings[0], file=sys.stderr)
else: print(e.errors[0], file=sys.stderr)