#!/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)