#!/p4/common/python/bin/python3 """ Copyright (c) 2012-2016 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 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 Cbd: 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, filemode='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. Display a reasonable error. 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) if (re.match ('[a-zA-Z]{1}:', path)): self.Log.debug("Windows drive letter detected in path. Normalizing to uppercase.") # Create a new string newPath = path[0].upper() i=1 while (i < len(path)): newPath += path[i] i += 1 path = newPath # Special Case: Is the path we are syncing from the workspace root? self.Log.debug ("Is workspace root? Checking ...") if (re.search ('\/\.\.\.$', path)): tempPath = re.sub ('\/\.\.\.$', '', path) endsIn3Dots=1 else: tempPath = path endsIn3Dots=0 self.Log.debug ("Comparing %s to %s." % (tempPath, self.WorkspaceRoot)) if self.WorkspaceRoot == tempPath: if endsIn3Dots: depotPath = "%s/..." % self.stream else: depotPath = self.stream self.Log.debug ("Path is workspace root. Returning stream root as depotPath: [%s]." % depotPath) return depotPath else: self.Log.debug ("Path is not workspace root. Continuing.") 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 ('[a-zA-Z]{1}:', path)): self.Log.debug("Path %s is an absolute path." % path) absolutePath = path else: self.Log.debug("Path %s is a relative path." % path) cwd = self.CWD # 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 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) if (re.match ('[a-zA-Z]{1}:', reqCwd)): self.Log.debug("Windows drive letter detected in CWD. Normalizing to uppercase.") # Create a new string newCWD = self.CWD[0].upper() i=1 while (i < len(self.CWD)): newCWD += self.CWD[i] i += 1 self.CWD = newCWD 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) if (re.match('[a-zA-Z]{1}:', self.WorkspaceRoot)): self.Log.debug("Windows drive letter detected in workspace root. Normalizing to uppercase.") # Create a new string newRoot = self.WorkspaceRoot[0].upper() i=1 while (i < len(self.WorkspaceRoot)): newRoot += self.WorkspaceRoot[i] i += 1 self.WorkspaceRoot = newRoot 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) dData = self.p4.fetch_depot (self.streamDepot) if (dData['StreamDepth']): self.streamDepth = re.sub ('^.*/', '', dData['StreamDepth']) self.streamDepth = int(self.streamDepth) else: self.streamDepth = 1 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) revSpecPart = '' if r'@' in anArg: revSpecPart = re.sub ('^.*@', '@', anArg) if r'#' in anArg: revSpecPart = re.sub ('^.*#', '#', anArg) depotSyntaxPath = self.getPathInDepotSyntax (pathPart) depotSyntaxPathWithRevSpec = depotSyntaxPath + revSpecPart self.Log.debug ("Looking for ...(@|#) or ...$ in [%s]." % depotSyntaxPathWithRevSpec) if (re.search ('\.\.\.(@|#)', depotSyntaxPathWithRevSpec) or re.search ('\.\.\.$', depotSyntaxPathWithRevSpec)): self.Log.debug ("Path contains '...[@|#] or ends with ...'.") slashes = re.findall ('/', depotSyntaxPath) slashCount = len(slashes) self.Log.debug ("Slash count for [%s] is %d." % (depotSyntaxPath, slashCount)) if (slashCount > (3+self.streamDepth)): 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 == (1 + self.streamDepth)): 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 == (2 + self.streamDepth)): 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 '//<imported_depot>/...' as non-global path." % pathArg) pathArgs.append(pathArg) treatAsGlobalPath = 0 if (slashCount == (3 + self.streamDepth)): 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")
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#24 | 19425 | C. Thomas Tyler |
Fixed issue with sync of a deep depot (StreamDepth>1) when stream spec has import entries. This causes Test 43 to pass. |
||
#23 | 19352 | C. Thomas Tyler |
Added utility script blog.py. Updated copyright year. |
||
#22 | 19295 | C. Thomas Tyler |
Changed logging 'mode' parameter to filemode. Need to look further into this, as 'mode=a' has worked in some environments, but it seems filemode='a' is needed in the test environment. |
||
#21 | 19271 | C. Thomas Tyler | Switched back to append-mode logging. | ||
#20 | 19253 | C. Thomas Tyler | Routine merge-down from main to dev. | ||
#19 | 16355 | C. Thomas Tyler |
Routine Merge Down to dev from main using: p4 -s merge -b perforce_software-cbd-dev |
||
#18 | 15630 | C. Thomas Tyler |
Fix for users getting 'ERROR: CBD was not able to translate path ' errors. This occured with sync commands from done specify the local OS syntax path from the root of a workspace associated with a 'complex' stream spec. |
||
#17 | 15358 | C. Thomas Tyler |
Tweaked test prep to use an uppercase drive letter in creation of test workspace earl_jams, to better test for Windows drive letter issues (Workshiop job job000330). Added new test for job000330, which initially failed (a valid repro). With changes in Cbd.py, it now passes. |
||
#16 | 15279 | C. Thomas Tyler |
Fixed issue recognizing sync from Linux. Fixed 'p4 sync ...' from root of workspace. Silenced 'is a complex relative path' log warning for simple '...' path. |
||
#15 | 15274 | C. Thomas Tyler |
Merge Down of cbd to dev from main using: p4 merge -b perforce_software-cbd-dev |
||
#14 | 15177 | C. Thomas Tyler |
Fixed various path translation issues that could result in a bad CBD sync. Now gives a useful error message if path translation fails, and aborts the sync. |
||
#13 | 15154 | C. Thomas Tyler |
Rewrote path translation logic. Now, rather than running 'p4 where' and parsing the output, we use P4Python's nifty P4.Map() feature to do comparisons locally. This has the added benefit for making at faster and more robust, as the old logic had issue with running 'p4 where' on behalf of users with locked clients or with the 'Host' field set. This also now handles paths in Workspace Syntax and Local OS syntax better, including absolute local paths, simple relative paths, and even some complex relative paths. |
||
#12 | 15039 | C. Thomas Tyler |
Merge Down CBD to dev from main to pick up test suite improvements and an exception bug fix in Cbd.py. |
||
#11 | 15008 | C. Thomas Tyler | Merge down from CBD mainline. | ||
#10 | 14947 | C. Thomas Tyler |
Adjusted file type, removing '+x', to indicate that Cbd.py is imported, not directly executed. No content changes. |
||
#9 | 14839 | C. Thomas Tyler | Updated to latest CBD from dev box. | ||
#8 | 14198 | C. Thomas Tyler |
Got Workshop up to date with latest version of CBD developed elsewhere. Added CbdDev.py to illustrate enabling testing a newer version on a live server, as a supplement to the test suite. This comes with supporting scripts wssync.dev.(sh,py) Added cmd_trig_by_auth.pl, a technology sample script. Not used presently. |
||
#7 | 13804 | C. Thomas Tyler | Fixed copy/paste errors. | ||
#6 | 13775 | C. Thomas Tyler |
Simplified logging and added support for logging with multiple SDP instances. |
||
#5 | 13773 | C. Thomas Tyler |
Added support for handling import+ entries. Fixed bug where excess keys were not cleaned up, e.g. if a stream spec was modified to delete an import entry. Corrected text in debug output. |
||
#4 | 11365 | C. Thomas Tyler |
Various enhancements and bug fixes in Cbd.py. Removed unneeded CbdComponent.py. |
||
#3 | 11360 | C. Thomas Tyler |
chmod +x. No content changes. |
||
#2 | 11348 | C. Thomas Tyler | Uploaded CBD scripts as presented at Merge 2014. | ||
#1 | 11347 | C. Thomas Tyler |
Populate //guest/perforce_software/cbd/dev/... from //guest/perforce_software/cbd/main/.... |
||
//guest/perforce_software/cbd/main/scripts/Cbd.py | |||||
#1 | 11196 | C. Thomas Tyler |
Added CBD sample logic. It was developed by Randy DeFauw as a PoC, and mentioned at the Perforce MERGE 2013 User Conference. This PoC explores a broad spectrum of CBD challenges, and thus is complex. Actual production implementations derived can be simpler. This implementation works with Classic Perforce only; support for Streams is possible. |
||
//guest/randy_defauw/cbd/scripts/Cbd.py | |||||
#1 | 8278 | Randy DeFauw | Import component development tool kit |