# # P4Triggers.py # # Version 2.0.4 # # Copyright (c) 2008-2016, Perforce Software, Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. 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 PERFORCE SOFTWARE, INC. 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. # # # Base class for all Python based P4 triggers # from __future__ import print_function import P4 from datetime import datetime import sys import os import logging import traceback import textwrap import argparse # If working on a server with the SDP, the 'LOGS' environment variable contains # the path the standard logging directory. The '-L <logfile>' argument shoudl be # specified in non-SDP environments. LOGDIR = os.getenv('LOGS', '/p4/1/logs') DEFAULT_LOG_FILE = "p4triggers.log" if os.path.exists(LOGDIR): DEFAULT_LOG_FILE = "%s/p4triggers.log" % LOGDIR DEFAULT_VERBOSITY = 'DEBUG' LOGGER_NAME = 'P4Triggers' class P4Change: """Encapsulates a Perforce change. Basically a pretty wrapping around p4.run_describe()""" def __init__(self, desc): self.change = desc["change"] self.user = desc["user"] self.client = desc["client"] self.desc = desc["desc"] self.time = datetime.utcfromtimestamp(int(desc["time"])) self.status = desc["status"] self.shelved = "shelved" in desc self.files = [] if "depotFile" in desc: for n, d in enumerate(desc["depotFile"]): df = P4.DepotFile(d) dr = df.new_revision() dr.type = desc["type"][n] dr.rev = desc["rev"][n] dr.action = desc["action"][n] self.files.append(df) self.jobs = {} if "job" in desc: for n, j in enumerate(desc["job"]): self.jobs[j] = desc["jobstat"][n] class P4Trigger(object): """Base class for Perforce Triggers""" def __init__(self, *args, **kwargs): """Constructor for P4Trigger. Keyword arguments are passed to the P4.P4() instance used""" kwargs['charset'] = 'none' # API Levels are defined here: http://answers.perforce.com/articles/KB/3197 # Ensure this does not exceed the value for the P4Python version used. # API Level 79 is for p4d 2015.2. kwargs['api_level'] = 79 self.p4 = P4.P4(**kwargs) self.options = None self.logger = None self.change = None def parse_args(self, doc, args): """Common parsing and setting up of args""" desc = textwrap.dedent(doc) parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=desc, epilog="Copyright (c) 2008-2017 Perforce Software, Inc." ) self.add_parse_args(parser) # Should be implemented by subclass self.options = parser.parse_args(args=args) self.init_logger() self.logger.debug("Command Line Options: %s\n" % self.options) def add_parse_args(self, parser, default_log_file=None, default_verbosity=None): """Default trigger arguments - common to all triggers :param default_verbosity: :param default_log_file: :param parser: """ if not default_log_file: default_log_file = DEFAULT_LOG_FILE if not default_verbosity: default_verbosity = DEFAULT_VERBOSITY parser.add_argument('-p', '--port', default=None, help="Perforce server port - set using %%serverport%%. Default: $P4PORT") parser.add_argument('-u', '--user', default=None, help="Perforce user. Default: $P4USER") parser.add_argument('-L', '--log', default=default_log_file, help="Default: " + default_log_file) parser.add_argument('-T', '--tickets', help="P4TICKETS file full path") parser.add_argument('-v', '--verbosity', nargs='?', const="INFO", default=default_verbosity, choices=('DEBUG', 'WARNING', 'INFO', 'ERROR', 'FATAL'), help="Output verbosity level. Default is: " + default_verbosity) def init_logger(self, logger_name=None): if not logger_name: logger_name = LOGGER_NAME self.logger = logging.getLogger(logger_name) self.logger.setLevel(self.options.verbosity) logformat = '%(levelname)s %(asctime)s %(filename)s %(lineno)d: %(message)s' logging.basicConfig(format=logformat, filename=self.options.log, level=self.options.verbosity) def setupP4(self): if self.options.port: self.p4.port = self.options.port if self.options.user: self.p4.user = self.options.user if self.options.tickets: self.p4.ticket_file = self.options.tickets self.p4.logger = self.logger self.logger.debug("P4 port: '%s', user: '%s'" % (self.p4.port, self.p4.user)) def parseChange(self, changeNo): try: self.p4.connect() self.setUp() self.change = self.getChange(changeNo) return 0 if self.validate() else 1 except Exception: return self.reportException() def getChange(self, changeNo, flag=None): if flag: result = self.p4.run_describe(flag, changeNo) else: result = self.p4.run_describe(changeNo) chg = P4Change(result[0]) if chg.shelved: # Files not listed by describe -s try: result = self.p4.run_files("@=%s" % changeNo) except: result = [] for f in result: df = P4.DepotFile(f['depotFile']) dr = df.new_revision() dr.type = f["type"] dr.rev = f["rev"] dr.action = f["action"] chg.files.append(df) return chg def validate(self): """Intended to be implemented in sub-class""" return True # method that sublasses can overwrite in order to complete the setup of P4 connection def setUp(self): pass def message(self, msg): """Method to send a message to the user. Just writes to stdout, but it's nice to encapsulate that here. :param msg: """ self.logger.info(msg) print(msg) def errorMessage(self): return """ An error was encountered during trigger execution. Please contact your Perforce administrator and ask them to investigate the cause of this error in %s """ % self.options.log def reportException(self): """Method to encapsulate error reporting to make sure all errors are reported in a consistent way""" exc_type, exc_value, exc_tb = sys.exc_info() self.message("Exception during trigger execution: %s %s %s" % (exc_type, exc_value, exc_tb)) self.reportP4Errors() self.logger.error("called from:\n%s", "".join(traceback.format_exception(exc_type, exc_value, exc_tb))) # return message to the user self.message(self.errorMessage()) return 1 def reportP4Errors(self): lines = [] for e in self.p4.errors: lines.append("P4 ERROR: %s" % e) for w in self.p4.warnings: lines.append("P4 WARNING: %s" % w) if lines: self.message("\n".join(lines))
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 24292 | gmc | "Forking branch Dev of perforce-software-sdp to gmc-sdp." | ||
//guest/perforce_software/sdp/dev/Server/Unix/p4/common/bin/triggers/P4Triggers.py | |||||
#15 | 23830 | Robert Cowham |
Refactor and move detection of shelved change up to P4Triggers. Also WorkflowTriggers |
||
#14 | 23671 | Robert Cowham | Tweak log setting for trigger logs | ||
#13 | 23670 | Robert Cowham | Properly test CheckFixes when no matching project found. | ||
#12 | 23526 | Robert Cowham | Fix typo | ||
#11 | 23242 | Robert Cowham |
Handle restricted changes (describe requires -f) Don't overwrite a changed changelist description |
||
#10 | 23152 | Robert Cowham | Refactor to push common arg_parse into P4Triggers | ||
#9 | 22854 | Robert Cowham | Addressed pylint suggestions. | ||
#8 | 22852 | Robert Cowham | Refactor reporting | ||
#7 | 22851 | Robert Cowham |
Works with job renaming now. Trigger needs to be form-in not form-save. |
||
#6 | 22850 | Robert Cowham | Refactored to move common stuff into P4Triggers.py | ||
#5 | 21528 | C. Thomas Tyler | Merged Sven's change from @21316: Enhanced error output to print the actual exception. | ||
#4 | 21286 | C. Thomas Tyler | SDP-ified log, moving p4triggers.log to ${LOGS}. | ||
#3 | 21120 | C. Thomas Tyler |
Corrected shebang line in CheckCaseTrigger. Added manual-update version id to replace keyword tag. |
||
#2 | 21101 | C. Thomas Tyler | SDP-ified; changed type to xtext, removed RCS keywoard. | ||
#1 | 21100 | C. Thomas Tyler | Branched P4Triggers.py | ||
//guest/robert_cowham/perforce/utils/triggers/P4Triggers.py | |||||
#2 | 19939 | Robert Cowham | Update with latest changes by Sven etc. | ||
#1 | 7531 | Robert Cowham | Personal branch | ||
//guest/sven_erik_knop/P4Pythonlib/triggers/P4Triggers.py | |||||
#5 | 7428 | Sven Erik Knop | Error output from Perforce is now written to the log file instead of stderr. | ||
#4 | 7379 | Sven Erik Knop |
Added output to a log file. The default is the send output to p4triggers.log in the P4ROOT directory, this can be overridden with the parameter log=<path> Also, errors now cause the trigger to fail with sensible output first. |
||
#3 | 7372 | Sven Erik Knop |
Rollback Rename/move file(s). To folder "perforce" is needed. |
||
#2 | 7370 | Sven Erik Knop | Rename/move file(s) again - this time to the right location inside a perforce directory. | ||
#1 | 7367 | Sven Erik Knop | New locations for the Python triggers. | ||
//guest/sven_erik_knop/perforce/P4Pythonlib/triggers/P4Triggers.py | |||||
#1 | 7370 | Sven Erik Knop | Rename/move file(s) again - this time to the right location inside a perforce directory. | ||
//guest/sven_erik_knop/P4Pythonlib/triggers/P4Triggers.py | |||||
#1 | 7367 | Sven Erik Knop | New locations for the Python triggers. | ||
//guest/sven_erik_knop/triggers/P4Triggers.py | |||||
#1 | 6413 | Sven Erik Knop |
Added some P4Python-based Perforce triggers. P4Triggers.py is the based class for change trigger in Python modelled on Tony Smith's Ruby trigger with the same name. CheckCaseTrigger.py is a trigger that ensures that no-one enters a file or directory with a name only differing by case from an existing file. This trigger is Unicode aware and uses Unicode-comparison of file names, so it can be used on nocase-Unicode based Perforce servers, which cannot catch the difference between, say, "�re" and "�re" at the moment. clienttrigger.py is a simple trigger that modifies the option "normdir" to "rmdir" for new client specs only. It is meant as a template to create more complex default settings like standard views. |