""" Copyright (c) 2012-2015 Perforce Software, Inc. To see expected sync bevhaviors for various forms of the 'p4 sync' command, run: /p4/common/bin/cbd/test/test_cbd.sh -man """ # Support methods for CBD # imports import subprocess import sys, types import P4 import logging import logging.config import os import ntpath, posixpath import re # DEBUG uncomment these two lines for remote debugging # import pydevd; # pydevd.settrace('192.168.1.15') class CbdDev: def __init__(self, logstyle): # constants # If in an SDP environment, P4BIN will be define. Otherwise, just set it to 'p4' and # expect an appropriate version to be on the path. self.P4Bin = os.getenv("P4BIN", "p4") self.Workspace = None self.WorkspaceRoot = None self.WorkspaceOptions = None self.WorkspaceOwner = None self.CSpec = None self.CBDSyncPathRules = {} self.p4 = P4.P4() # globals to read from config files self.P4USER = None # If in an SDP environment, LOGS will be set to the log dir. Otherwise use $CBD_HOME/logs. # We assume CBD_HOME is set. self.LogDir = os.getenv ("LOGS", "%s/logs" % os.getenv ("CBD_HOME")) self.LogFile = "%s/%s" % (self.LogDir, os.getenv ("CBD_LOG", "cbd.log")) self.Log = None # set up logging and read config data self.initLog(logstyle) def printRewrite(self, msg): self.Log.debug("Rewrite: %s" % msg) print (msg) # set up logging def initLog(self, logstyle): format='%(levelname)s [%(asctime)s] [%(funcName)s : %(lineno)d] - %(message)s' logging.basicConfig(format=format, filename=self.LogFile, mode='a', level=logging.DEBUG) self.Log = logging.getLogger(logstyle) # make logger available externally def getLogger(self): return self.Log def __del__(self): # disconnect if self.p4.connected(): self.p4.disconnect() # Connect to Perforce def initP4(self, p4port): self.p4.prog = 'CBD' self.p4.port = p4port self.p4.user = os.getenv ('P4USER', 'perforce') self.p4.ticket_file = os.getenv ('P4TICKETS', 'UNDEFINED_P4TICKETS_VALUE') self.Log.debug("Using P4USER=%s" % self.p4.user) self.Log.debug("Using P4PORT=%s" % self.p4.port) self.Log.debug("Using P4TICKETS=%s" % self.p4.ticket_file) try: self.p4.connect() return True except P4.P4Exception: self.Log.error("Unable to connect to Perforce at P4PORT=%s" % p4port) for e in self.p4.errors: self.Log.error("Errors: " + e) for w in self.p4.warnings: self.Log.warn("Warnings: " + w) return False # Parse broker arguments and store them in a dictionary for # easy access. def parseBrokerArgs(self): vals = dict() data = sys.stdin.readlines() for arg in data: arg.rstrip('\n') # self.Log.debug("Read %s" % arg) m = re.match("^(.+?):\s+(.+)$", arg) k = m.group(1) v = m.group(2) vals[k] = v # self.Log.debug("Arg %s = %s" % (k, v)) return vals # Load client spec fields into self.CSpec; do a 'p4 client -o' (aka fetch). def loadWorkspaceInfo(self): self.Log.debug("CALL loadWorkspaceInfo (%s)" % self.Workspace) try: self.CSpec = self.p4.fetch_client (self.Workspace) self.Log.debug("CSPEC = %s " % self.CSpec) return self.CSpec except: self.Log.error("Could not get client spec named [%s]." % self.Workspace) for e in self.p4.errors: # Display errors self.Log.debug(e) return None # Return 'Field:' field of the workspace. def getWorkspaceField (self, field): self.Log.debug("CALL getWorkspaceField (%s)" % field) if (not self.CSpec): self.loadWorkspaceInfo() try: value = self.CSpec[field] self.Log.debug("Field %s value is %s." % (field, self.CSpec[field])) return value except: self.Log.debug("Field %s not defined in CSPEC." % field) return None def getPathInDepotSyntax (self, path): # Translate the user-supplied path to depot syntax. self.Log.debug("CALL getPathInDepotSyntax (%s)" % path) # Path Possibilities: # 1. Depot syntax, e.g. //fgs/main/src/Hello.c # 2. Workspace syntax, e.g. //my_ws/src/Hello.c # 3. Local OS syntax, absolute, e.g. C:/p4/my_ws/src/Hello.c # 4. Local OS syntax, simple relative, e.g. Hello.c, src/Hello.c, ./Hello.c, etc. # 5. Local OS syntax, complex relative, e.g. ../src/Hello.c, etc. # 6. Path is invalid. WorkspaceSyntaxTest = '//%s/' % self.Workspace if re.search (r'//', path): if not re.match (WorkspaceSyntaxTest, path): self.Log.debug("Path (%s) is already in depot syntax." % path) return path if (not self.CSpec['View']): self.Log.debug("Workspace has no View field! Skipping path translation.") return path viewWithoutExclusions = [] for viewEntry in self.CSpec['View']: if not re.search (r'-//', viewEntry): viewWithoutExclusions.append(viewEntry) if re.search (r'//', path): self.Log.debug("Translating path in Workspace Syntax [%s]." % path) depotToWorkspaceMap = P4.Map() for viewEntry in viewWithoutExclusions: self.Log.debug("VE: %s" % viewEntry) depotToWorkspaceMap.insert (viewEntry) depotPath = depotToWorkspaceMap.translate (path, 0) self.Log.debug("Translated path [%s] to depot syntax as [%s]." % (path, depotPath)) return depotPath self.Log.debug("Translating Local OS Syntax path [%s]." % path) depotToWorkspaceMap = P4.Map() workspaceToLocalMap = P4.Map() for viewEntry in viewWithoutExclusions: self.Log.debug("VE: %s" % viewEntry) depotToWorkspaceMap.insert (viewEntry) tmpMap = P4.Map() tmpMap.insert(viewEntry) dPath = tmpMap.lhs()[0] wPath = tmpMap.rhs()[0] lPath = re.sub ('//.*?/', self.WorkspaceRoot + '/', wPath) self.Log.debug("Map: Depot [%s] Workspace [%s] Local [%s]." % (dPath, wPath, lPath)) workspaceToLocalMap.insert (wPath, lPath) tmpMap.clear() self.Log.debug("Depot to Workspace Map BEFORE:\n%s" % depotToWorkspaceMap) newDepotToWorkspaceMap = P4.Map() for entry in depotToWorkspaceMap.as_array(): if re.search (r'-//', entry): continue self.Log.debug("NVE: [%s]." % entry) newDepotToWorkspaceMap.insert (entry) self.Log.debug("New Map is now:\n%s" % newDepotToWorkspaceMap) depotToWorkspaceMap.clear() depotToWorkspaceMap = newDepotToWorkspaceMap self.Log.debug("Depot to Workspace Map AFTER:\n%s" % depotToWorkspaceMap) depotToLocalMap = P4.Map.join (depotToWorkspaceMap, workspaceToLocalMap) self.Log.debug("Depot to Workspace Map:\n%s\n\nWorkspace To Local Map:\n%s\n\nJoined Depot to Local Map:%s\n\n" % (depotToWorkspaceMap, workspaceToLocalMap, depotToLocalMap)) if (re.match (r'/', path) or re.match (r'[a-z]|[A-Z]{1}:', path)): self.Log.debug("Path %s is an absolute path." % path) if (re.match (r'[a-z]|[A-Z]{1}:', path) and re.match(r'[a-z]|[A-Z]{1}:', self.WorkspaceRoot)): self.Log.debug("Compare first char (drive letter) of path [%s] and Workspace Root [%s]." % (path, self.WorkspaceRoot)) if ((path[0] != self.WorkspaceRoot[0]) and (path[0].upper() == self.WorkspaceRoot[0].upper())): self.Log.debug("Drive letter varies in case only. Fixing that.") # Create a new string newPath = self.WorkspaceRoot[0] i=1 while (i < len(path)): newPath += path[i] i += 1 path = newPath absolutePath = path else: self.Log.debug("Path %s is a relative path." % path) cwd = self.CWD if (re.match (r'[a-z]|[A-Z]{1}:', path) and re.match(r'[a-z]|[A-Z]{1}:', self.WorkspaceRoot)): self.Log.debug("Compare first char (drive letter) of path [%s] and Workspace Root [%s]." % (path, self.WorkspaceRoot)) if ((path[0] != self.WorkspaceRoot[0]) and (path[0].upper() == self.WorkspaceRoot[0].upper())): self.Log.debug("Drive letter varies in case only. Fixing that.") # Create a new string newPath = self.WorkspaceRoot[0] i=1 while (i < len(path)): newPath += path[i] i += 1 path = newPath # For each '../' in the user-supplied path, remove a directory level # from the current directory of the user. if re.match (r'\.\./', path): self.Log.debug("Relative path [%s] starts with '..'." % path) while re.match (r'\.\./', path): path = re.sub (r'^\.\./', '', path) cwd = os.path.dirname(cwd) if (re.search (r'\.\.', path)): self.Log.warn("Path %s is a complex relative path with embedded '..'. Not sure how well we handle it." % path) # If the user specified path starts with './', just trim it # since it will foil our mapping translatoins. absolutePath = re.sub (r'^\./', '', cwd) + path self.Log.debug("Absolute path [%s] derived from relative path [%s]." % (absolutePath, path)) depotPath = depotToLocalMap.translate (absolutePath, 0) if not depotPath: self.printRewrite("action: REJECT") self.printRewrite("message: ERROR: CBD was not able to translate path [%s] to depot syntax. If you are using P4V, try using the Depot Tab instead of the Workspace tab." % path) sys.exit(1) self.Log.debug("Translated path [%s] to depot syntax as [%s]." % (path, depotPath)) return depotPath def loadSyncPathsFromFile (self, reqWs, stream, vSpec): """ loadSyncPathsFromFile() """ self.Log.debug("CALL loadSyncPathsFromFile(%s, %s)" % (reqWs, stream)) # Find the right version of the Stream Spec Template (.cbdsst) file: sstFileFinder = "%s/....cbdsst%s" % (stream, vSpec) try: self.Log.debug ("Running: p4 files -m1 -e %s" % sstFileFinder) sstFileData = self.p4.run_files ('-m1', '-e', sstFileFinder) except P4.P4Exception: for e in self.p4.errors: self.Log.error("Errors: " + e) for w in self.p4.warnings: self.Log.warn("Warnings: " + w) self.Log.warning ("Could not access Stream Spec Template file.") # we are just going to throw a warning now and pass the command through # not having a CBD file in old versions should abide by old sync rules return # This will be something like: //FGS/dev/FGS.cbdsst#8 sstFileRev = "%s#%s" % (sstFileData[0]['depotFile'], sstFileData[0]['rev']) try: self.Log.debug ("Running: p4 print -q %s" % sstFileRev) sstFileContentsData = self.p4.run_print ('-q', sstFileRev) except P4.P4Exception: self.printRewrite("action: REJECT") self.printRewrite("message: ERROR: CBD Could not print stream spec template file [%s]. Sync not performed." % sstFileRev) sys.exit(1) # The p4.run_print() call returns an array, with sstFileContentsData[0] # containing metadata and sstFileContentsData[1] containing file contents. sstFileContents = sstFileContentsData[1] pathEntriesStarted = False # Extract Path 'import' and 'import+' lines and their revision specifiers. for line in sstFileContents.split('\n'): if (re.match ('Paths\:', line)): pathEntriesStarted = True continue if (pathEntriesStarted == False): continue line = line.strip() if (re.match ('share ', line)): # shortPath is just what follows the 'share' token in the # 'Paths:' field entry of a stream spec. # sharePath is the fully-qualified form of the shortPath following # the 'share' token in values in the 'Paths:' field of the stream # spec. It is obtained by prefixing shortPath with the stream # name and '/'. So for 'share src/...' in the //Jam/MAIN # stream, sharePath would be "//Jam/MAIN/src/...". # PH - This shortPath isn't used? # shortPath = re.sub ('^share ', '', line) sharePath = "%s/%s" % (self.stream, re.sub ('^share ', '', line)) self.CBDSyncPathRules [sharePath] = vSpec self.Log.debug ("Added CBDSyncPathRules[%s%s]." % (sharePath, vSpec)) if (re.match ('import ', line) or re.match ('import+ ', line)): # If a revsion specifier was found on the import line, store it. revSpec = '#head' if (re.search('#', line)): revSpec = line revSpec = re.sub('^.*#', '#', line) if (re.search('@', line)): revSpec = line revSpec = re.sub('^.*@', '@', line) depotPath = re.sub ('^.*//', '//', line) depotPath = re.sub ('(#|@).*$', '', depotPath) self.CBDSyncPathRules [depotPath] = revSpec self.Log.debug ("Added CBDSyncPathRules[%s%s]." % (depotPath, revSpec)) def loadSyncPathsFromKeys (self, reqWs, stream): """ loadSyncPathsFromKeys() This gets the default sync paths, those stored in 'keys' on the Peforce server, for the given stream. The sync paths are stored in the self.CBDSyncPathRules class variable, a dictionary with keys being the path and values being the version spec for that path. """ self.Log.debug("CALL loadSyncPathsFromKeys(%s, %s)" % (reqWs, stream)) shortStreamName = re.sub (r'^//', '', stream) shortStreamName = re.sub (r'/', '_', shortStreamName) pathKeySearchString = "cbd_stream_%s_path*" % shortStreamName vspecKeySearchString = "cbd_stream_%s_vspec*" % shortStreamName self.Log.debug ("Searching for path keys: %s" % pathKeySearchString) self.Log.debug ("Searching for vspec keys: %s" % vspecKeySearchString) # These p4.run_keys calls return arrays of keys, one returning an # array of path elements, and another an array of version # specifiers. # We assume that the number of values returned, which corresponds # to the number of lines of output of our 'p4 keys' command, is # is the same for both the path and vspec variants of our key # search command. It's the job of the StreamSpecUpdate trigger # to ensure this assumption is safe. pathList = self.p4.run_keys ("-e", pathKeySearchString) vspecList = self.p4.run_keys ("-e", vspecKeySearchString) i = 0 for p in pathList: # Add the version specifer for this path, which is in the # vspecList list with the same index as the path from # pathList. Add is at a two-part tuple consisting of the # path and the vspec. self.Log.debug ("i=%s" % i) self.Log.debug ("p=%s" % p['value']) self.Log.debug ("v=%s" % vspecList[i]['value']) self.CBDSyncPathRules[p['value']] = vspecList[i]['value'] self.Log.debug ("Added CBDSyncPathRules[%s%s]." % (p['value'], vspecList[i]['value'])) i = i + 1 def syncWs(self, reqWs, reqCmd, reqCwd, reqArgs): self.Log.info("Syncing workspace %s with arguments %s" % (reqWs, ",".join(reqArgs))) self.Workspace = reqWs self.CWD = re.sub (r'\\', r'/', reqCwd) try: stream = self.getWorkspaceField('Stream') if stream == None: self.Log.debug("Ignoring classic workspace %s (for now)." % reqWs) print ("action: PASS\n") return True self.WorkspaceRoot = self.getWorkspaceField('Root') self.WorkspaceRoot = re.sub (r'\\', r'/', self.WorkspaceRoot) self.Log.debug("Workspace Root: %s." % self.WorkspaceRoot) self.WorkspaceOptions = self.getWorkspaceField('Options') self.Log.debug("Workspace Options: %s." % self.WorkspaceOptions) self.WorkspaceOwner = self.getWorkspaceField('Owner') self.Log.debug("Workspace Owner: %s." % self.WorkspaceOwner) self.Log.debug("Syncing with command: p4 %s" % reqCmd) self.stream = stream self.streamDepot = re.sub ('//', '', stream) self.streamDepot = re.sub ('/.*$', '', self.streamDepot) flagArgs = [] pathArgs = [] self.globalVSpec = '' # default to global path unless found otherwise treatAsGlobalPath = 1 for anArg in reqArgs: if anArg.startswith("-"): flagArgs.append(anArg) continue # "Global" version specifiers are those specified with no path, e.g. # 'p4 sync @5036'. In a stock Perforce, that applies to the enitre # scope of the workspace. In CBD, it determines which stream spec # template to use, which in turn drives which revisions of imported # files to sync. If multiple global specifiers are specifed, only # last one has meaning. if (anArg.startswith("@") or anArg.startswith("#")): if not self.globalVSpec == '': self.Log.warn ("Multiple global version specifers defined. Ignoring earlier ones.") self.globalVSpec = anArg self.Log.debug ("Global version specifier set to: [%s]." % self.globalVSpec) continue # For paths containing explicit revision specifiers, just apply the # given specifier. # For other paths, determine if the user-supplied path is within the # scope of a CBD-defined path (e.g. //fgs/main/src/foo.c if //fgs/main/... # is governed by CBD). Then select and apply the appropriate CBD rule. # If a path arg is just "//...@vSpec", treat the version specifier as # global, just as if there were no path component. # Strip '#head', since P4V blindly applies it. pathArg = re.sub (r'#head$', '', anArg) self.Log.debug ("Normalizing pathArg to forward slashes.") pathArg = re.sub (r'\\', r'/', pathArg) # pathPart is strictly the path part of pathArg, excluding any revision specifiers. pathPart = re.sub ('(@|#).*', '', pathArg) depotSyntaxPath = self.getPathInDepotSyntax (pathPart) self.Log.debug ("Looking for ^// or X: and ...@* or ...$) in [%s]." % pathArg) if ((re.match (r'//', pathArg) or re.match ('[a-z|A-Z]{1}:', pathArg)) and (re.search (r'/\.\.\.@', pathArg) or re.search(r'/\.\.\.$', pathArg) or re.search (r'/\.\.\.#', pathArg))): self.Log.debug ("Path starts with '//' or X: and contains '...[@|#]*'.") slashes = re.findall ('/', depotSyntaxPath) slashCount = len(slashes) self.Log.debug ("Slash count for [%s] is %d." % (depotSyntaxPath, slashCount)) if (slashCount > 4): self.Log.debug ("Deferring handling of narrow path arg [%s]." % pathArg) pathArgs.append(pathArg) # defer to not a globalpath self.Log.debug ("Treating path arg [%s] as non-global." % pathArg) treatAsGlobalPath = 0 else: self.Log.debug ("Checking [%s] for global version specifiers." % pathArg) if (slashCount == 2): vSpec = "#head" if (re.search (r'...@', pathArg)): vSpec = re.sub('^.*\.\.\.@', '@', pathArg) if (re.search (r'...#', pathArg)): vSpec = re.sub('^.*\.\.\.#', '#', pathArg) if not self.globalVSpec == '': self.Log.warn ("Multiple global version specifers defined. Ignoring earlier ones.") self.globalVSpec = vSpec self.Log.debug ("Global version specifier set to: %s." % self.globalVSpec) if (slashCount == 3): lvl1 = re.sub ('//', '', pathArg) lvl1 = re.sub ('/.*$', '', lvl1) if (re.search (':', lvl1)): self.Log.debug ("Windows Local OS Syntax detected. Does pathArg [%s] contain Workspace Root [%s]?" % (pathArg, self.WorkspaceRoot)) if (re.match (self.WorkspaceRoot, pathArg, re.IGNORECASE)): self.Log.debug ("Sync from workspace root detected.") treatAsGlobalPath = 1 vSpec = "#head" if (re.search (r'...@', pathArg)): vSpec = re.sub('^.*\.\.\.@', '@', pathArg) if (re.search (r'...#', pathArg)): vSpec = re.sub('^.*\.\.\.#', '#', pathArg) if not self.globalVSpec == '': self.Log.warn ("Multiple global version specifers defined. Ignoring earlier ones.") self.globalVSpec = vSpec self.Log.debug ("Global version specifier set to: %s." % self.globalVSpec) else: self.Log.debug ("Comparing lvl1=[%s] with stream depot name [%s] and workspace name [%s]." % (lvl1, self.streamDepot, self.Workspace)) if (lvl1 == self.streamDepot or lvl1 == self.Workspace): vSpec = "#head" if (re.search (r'...@', pathArg)): vSpec = re.sub('^.*\.\.\.@', '@', pathArg) if (re.search (r'...#', pathArg)): vSpec = re.sub('^.*\.\.\.#', '#', pathArg) if not self.globalVSpec == '': self.Log.warn ("Multiple global version specifers defined. Ignoring earlier ones.") self.globalVSpec = vSpec self.Log.debug ("Global version specifier set to: %s." % self.globalVSpec) else: self.Log.debug ("Treating path %s of form '///...' as non-global path." % pathArg) pathArgs.append(pathArg) treatAsGlobalPath = 0 if (slashCount == 4): if (re.search (r'...@', pathArg) or re.search(r'...#', pathArg)): self.Log.debug ("Has vSpec.") pathPart = re.sub ('\.\.\.@.*$', '', pathArg) pathPart = re.sub ('\.\.\.#.*$', '', pathArg) pathPart = re.sub ('\/$', '', pathPart) else: self.Log.debug ("No vSpec.") pathPart = re.sub ('\.\.\.$', '', pathArg) pathPart = re.sub ('\/$', '', pathPart) self.Log.debug ("Comparing lvl1/lvl2=[%s] with stream name [%s]." % (depotSyntaxPath, self.stream)) if (re.match (self.stream, depotSyntaxPath)): vSpec = "#head" if (re.search (r'...@', pathArg)): vSpec = re.sub('^.*\.\.\.@', '@', pathArg) if (re.search (r'...#', pathArg)): vSpec = re.sub('^.*\.\.\.#', '#', pathArg) if not self.globalVSpec == '': self.Log.warn ("Multiple global version specifers defined. Ignoring earlier ones.") self.globalVSpec = vSpec self.Log.debug ("Global version specifier set to: %s." % self.globalVSpec) else: self.Log.debug ("Treating path arg [%s] as non-global. Deferring handling." % pathArg) treatAsGlobalPath = 0 pathArgs.append(pathArg) else: self.Log.debug ("Deferring handling of non-global path arg [%s]." % pathArg) treatAsGlobalPath = 0 pathArgs.append(pathArg) # Populate the 'CBDSyncPathRules' dictionary with path/vspec pairs, either from keys or a file. if self.globalVSpec == '' or self.globalVSpec == '#head': self.loadSyncPathsFromKeys (reqWs, stream) else: self.loadSyncPathsFromFile (reqWs, stream, self.globalVSpec) if not self.CBDSyncPathRules: self.Log.debug ("No CBD Keys found. Passing user command thru.") self.printRewrite("action: PASS\n") return if (treatAsGlobalPath == 1): self.Log.debug ("Handling rewrite for global sync.") self.printRewrite("action: REWRITE") self.printRewrite("command: %s" % reqCmd) # Write the flag args. for anArg in flagArgs: self.printRewrite("arg: %s" % anArg) notes = [] notes.append("CBD %s %s" % (reqCmd, flagArgs)) # Write that path/vspec args with CBD-defined vspecsif we have nay for k in self.CBDSyncPathRules.keys(): self.Log.debug("K=%s" % k) self.printRewrite("arg: %s%s" % (k, self.CBDSyncPathRules[k])) notes.append("%s%s" % (k, self.CBDSyncPathRules[k])) if (len (notes) > 0): print ("message: \"INFO: %s.\"" % notes) self.Log.debug ("User Message: INFO: %s" % notes) if (treatAsGlobalPath == 0 and len(pathArgs) > 0): self.Log.debug("Processing user-supplied path args: %s" % pathArgs) self.printRewrite("action: REWRITE") self.printRewrite("command: %s" % reqCmd) for anArg in flagArgs: self.printRewrite("arg: %s" % anArg) for pathArg in pathArgs: """ For each path in the user-supplied list of paths, first check for an explicit version specifier provided by the user. If one is found, use it, and provide a warning that CBD version specifiers are ignored for that path. If no explicit version specifier is provided by the user, determine the corresponding CBD version specifier (if any). Lastly, REWRITE each path as a separate 'p4 sync' command. """ notes = [] notes.append("CBD %s %s" % (reqCmd, flagArgs)) # For path arguments for which the user supplied an explicit revision specifer, # just use it. self.Log.debug("Checking [%s] for an explicit revision specifier." % pathArg) if (re.search ('@|#', pathArg)): self.Log.debug ("Ignoring CBD rules in favor of explicit revision specifier for path [%s]." % pathArg) self.printRewrite("arg: %s" % pathArg) notes.append(pathArg) else: # In this block, we can safely assume pathArg does not contain a revision specifier. self.Log.debug ("Seeking CBD rule for pathArg [%s]." % depotSyntaxPath) # pathPart is strictly the path part of pathArg, excluding any revision specifiers. depotSyntaxPath = self.getPathInDepotSyntax (pathArg) # Strip the leading '//' for easier comparison. depotPath = re.sub (r'^//', '', depotSyntaxPath) pathFound = 0 self.Log.debug ("Seeking depot path [%s] in CBDSyncPathRules.keys dictionary." % depotPath) for k in self.CBDSyncPathRules.keys(): try: kcheck = k.rstrip(r'/...') + '/' kcheck = re.sub (r'^//', '', kcheck) self.Log.debug ("if (%s) starts with (%s) ..." % (depotPath, kcheck)) if (depotPath.startswith(kcheck)): self.printRewrite("arg: %s%s" % (pathArg, self.CBDSyncPathRules[k])) notes.append("%s%s" % (pathArg, self.CBDSyncPathRules[k])) pathFound = 1 break except: self.Log.error ("Exception checking applicability of CBD rule [%s] to path [%s]." % (self.CBDSyncPathRules[k]), pathArg) if (pathFound == 0): self.Log.debug ("No explicit vspec or CBD rule found. Passing thru.") self.printRewrite("arg: %s" % pathArg) notes.append("Path [%s] is out of CBD scope. Assuming #head." % pathArg) if (len (notes) > 0): print ("message: \"INFO: %s.\"" % notes) self.Log.debug ("User Message: INFO: %s" % notes) self.Log.debug ("Path processing complete.") except P4.P4Exception: errors = [] for e in self.p4.errors: self.Log.error("Errors: " + e) errors.append(e) for w in self.p4.warnings: self.Log.warn("Warnings: " + w) errors.append(w) print ("action: REJECT\nmessage: \"CBD Error syncing workspace: %s\"" % ",".join(errors)) return False except Exception as e: self.Log.debug("Passing thru due to exception: %s.\n" % e) print ("action: PASS\n")