#!/bin/env python3 ''' Perforce Baseline and Branch Import Tool Configuration Validator Copyright (c) 2012 Perforce Software, Inc. 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. THIS SOFTWARE IS NOT OFFICIALLY SUPPORTED. Please send any feedback to consulting@perforce.com. ''' import sys import os import re import platform from optparse import OptionParser from subprocess import * import shutil import stat import tarfile import zipfile import hashlib import tempfile P4USER="p4admin" P4PORT="perforce:1666" P4CLIENT="bbi_import" mode="1" verbosity="2" p4cfg_file="test" bbicfg_file="" removejunk=False if platform.system() == "Windows": p4="p4.exe" else: p4="p4" valid_commands = ("BRANCH", "UPDATE", "SAFEUPDATE", "COPY_MERGE", "RECORD_AS_MERGED", "RENAME") valid_options = ("EMPTYDIRS", "SYMLINKS", "BRANCHSPEC", "NODELETE", "ALLOWWILDCARDS", "RMTOP", "FULLCOPY") def parseoptions(): global mode global verbosity global p4cfg_file global bbicfg_file global removejunk usage = "usage: %prog [options] P4CONFIG_FILE BBI_CONFIG_FILE" parser = OptionParser(usage) parser.add_option("-m", "--mode", dest="mode", help="Mode: 1 = check file metadata, 2 = check file contents.") parser.add_option("-r", "--remove", dest="rjd", help="Remove junk dirs. Removes known old SCM files and directories.") parser.add_option("-v", "--verbosity", dest="level", help="Specify the verbosity level, from 1-3. 1 is quiet mode;" " only error and test output is displayed. 2 is normal verbosity; some messages" " displayed. 3 is debug-level verbosity. The default for this program is 3.") (options, args) = parser.parse_args() if len(args) < 2: parser.error("Run p4bbi_validate.py -h for help.") if not os.path.isfile(args[0]): log("ERROR", "P4CONFIG file: '{0}' not found.".format(args[0])) else: p4cfg_file = args[0] if not os.path.isfile(args[1]): log("ERROR", "p4bbi config file: '{0}' not found.".format(args[1])) else: bbicfg_file=args[1] if options.mode: try: if 0 < int(options.mode) < 3: mode = options.mode print("Mode level set to: {0}.".format(options.mode)) else: sys.exit(1) except: log("ERROR", "Mode level must be an integer value of 1 or 2.") if options.rjd: removejunk = True log("INFO", "Junk directories were removed from new Perforce server.") if options.level: try: if 0 < int(options.level) < 4: verbosity = options.level print("Verbosity level set to: {0}.".format(options.level)) else: sys.exit(1) except: log("ERROR", "Verbosity level must be an integer value of 1, 2 or 3.") def loadp4config(): global P4USER global P4PORT global P4CLIENT cfgfile = open(p4cfg_file, "r") for line in cfgfile.readlines(): if re.search(r"^P4USER=", line): P4USER = re.match(r"^P4USER=(.*)", line).group(1) continue if re.search(r"^P4PORT=", line): P4PORT = re.match(r"^P4PORT=(.*)", line).group(1) continue if re.search(r"^P4CLIENT=", line): P4CLIENT = re.match(r"^P4CLIENT=(.*)", line).group(1) continue cfgfile.close() def loadbbiconfig(): baselines = {} commands = [] cfgfile = open(bbicfg_file, "r") referenced_baseline_found = False log("DEBUG", "Loading info from {0}.".format(bbicfg_file)) for line in cfgfile.readlines(): if (re.search("^#", line) or re.search("^\s*$", line)): continue if re.search("^BASELINE", line): (name,path) = re.match("^BASELINE\|(.*)\|(.*)", line).groups() if not name: cfgfile.close() log("ERROR", "Error in Baseline definition for {0}.".format(line)) elif not (os.path.isdir(path) or os.path.isfile(path) or path.startswith("CMD:")): cfgfile.close() log("ERROR", "Baseline path {0} not found.".format(path)) else: log("DEBUG", "Verified path: {0}".format(path)) log("DEBUG", "Baseline {0} loaded.".format(name)) baselines[name] = path else: try: cmdline_match = re.match("(.*)\|(.*)\|(.*)\|(.*)\|(.*)", line) if cmdline_match == None: cmdline_match = re.match("(.*)\|(.*)\|(.*)\|(.*)", line) cmdline_items = cmdline_match.groups() if len(cmdline_items) == 5: options = cmdline_items[4] options_list = options.split(",") for o in options_list: parts = o.split("=") if o not in valid_options and parts[0] not in valid_options: log("ERROR", "Invalid option {0} specified in {1}".format(o, line)) if o == 'SYMLINKS' and platform.system() == "Windows": log("ERROR", "Invalid option {0} specified: SYMLINKS not applicable on Windows".format(o)) if parts[0] == 'BRANCHSPEC' and len(parts) != 2: log("ERROR", "Invalid option {0} specified: BRANCHSPEC option must include a branch spec name".format(o)) if cmdline_items[0] in valid_commands: commands.append(cmdline_items) log("DEBUG", "Command line: {0} loaded.".format(cmdline_items)) else: log("ERROR", "Invalid command in line:\n {0}".format(line)) except: log("ERROR", "Invalid line in config file:\n {0}".format(line)) if cmdline_items[0] == "UPDATE": referenced_baseline_found = False for key in iter(baselines): if cmdline_items[1] == key: referenced_baseline_found = True if not referenced_baseline_found: cfgfile.close() log("ERROR", "Baseline {0} referenced, but not defined.".format(referenced_baseline)) cfgfile.close() if baselines == {}: log("ERROR", "No baselines found in config file.") return baselines, commands def md5_for_file(fname, block_size=2**20): m = hashlib.md5() f = open(fname, "r") while True: data = f.read(block_size) if not data: break m.update(data) f.close() return m.hexdigest() def log(msglevel="DEBUG", message=""): if msglevel == "ERROR": print(message) sys.exit(1) elif (verbosity == "3"): print(message) elif (verbosity == "2" and msglevel == "INFO"): print(message) # delete tree def rmtree(treedir): try: for root, dirs, names in os.walk(treedir): for name in names: os.chmod(os.path.join(root,name), stat.S_IWRITE) shutil.rmtree(treedir) except OSError as e: log("ERROR", "Could not remove {0}.\nThe error was:\n{1}".format(treedir, e)) # run OS cmd def runcmd(cmd): try: log("DEBUG", cmd); pipe = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, universal_newlines=True) stdout, stderr = pipe.communicate() log("DEBUG", stdout) if pipe.returncode != 0: log("INFO", "{0} generated the following output: {1}".format(cmd, stdout)) log("ERROR", "{0} generated the following error: {1}".format(cmd, stderr)) else: return stdout except OSError as err: log("ERROR", "Execution failed: {0}".format(err)) def runp4cmd(cmd): try: pipe = Popen(p4 + cmd, shell=True, stdin=PIPE, stdout=PIPE, universal_newlines=True) stdout, stderr = pipe.communicate() log("DEBUG", stdout) if pipe.returncode != 0: log("ERROR", "{0}{1} generated the following error: {2}".format(p4, cmd, stderr)) else: return stdout except OSError as err: log("ERROR", "Execution failed: {0}".format(err)) def getclientpath(p4dir): result = runp4cmd("where {0}".format(p4dir)) mappings = result.split() if platform.system() == "Windows": localdir = mappings[2].replace("\\...", "") else: localdir = mappings[2].replace("/...", "") return localdir def processcommands( baselines={}, commands=[]): """ Each command contains four components as follows: UPDATE|BaselineName|P4Dir|Description SAFEUPDATE|BaselineName|P4Dir|Description BRANCH|SourcePath|TargetPath|Description COPY_MERGE|SourcePath|TargetPath|Description RECORD_AS_MERGED|SourcePath|TargetPath|Description RENAME|SourcePath|TargetPath|Description """ for cmd in commands: desc = cmd[3].replace("$N", "\n") options = None if len(cmd) == 5: options = cmd[4] if cmd[0] == "UPDATE": log("DEBUG", "\np4update({0}, {1}, {2}, {3}, {4}, {5})".format(cmd[0], baselines[cmd[1]], cmd[2], desc, False, options)) p4update(cmd[0], baselines[cmd[1]], cmd[2], desc, False, options) else: if cmd[0] == "SAFEUPDATE": log("DEBUG", "\np4update({0}, {1}, {2}, {3}, {4}, {5})".format(cmd[0], baselines[cmd[1]], cmd[2], desc, True, options)) p4update(cmd[0], baselines[cmd[1]], cmd[2], desc, True, options) else: log("DEBUG", "\np4branch({0}, {1}, {2}, {3}, {4})".format(cmd[0], cmd[1], cmd[2], desc, options)) p4branch(cmd[0], cmd[1], cmd[2], desc, options) def get_changelist(p4dir, desc): changelists = runp4cmd("changes -l {0}".format(p4dir)) changes = re.findall(r"Change (\d+) on", changelists) matches = re.split(r"Change \d+ on", changelists) ii = 0 change = '' for match in matches: if len(match.strip()) < 1: continue lines = match.splitlines()[2:] cdesc = "\n".join(lines) cdesc = cdesc.strip() if cdesc == desc: change = changes[ii] break ii = ii+1 if len(change) < 1: cnt = int(runp4cmd("counter bbi_cnt").strip()) for ii in range(1, cnt+1): cvalue = runp4cmd("counter bbi_{0}".format(ii)) (achange,adesc) = re.match(r"(\d+)=(.+)", cvalue).groups() if adesc == desc: change = achange break return change def p4update(cmd, baseline, p4dir, desc, safe, options = None): options_list = [] if options != None: options_list = options.split(",") topdir = baseline orig_topdir = False if baseline.startswith("CMD:"): topdir = tempfile.mkdtemp(dir=os.getcwd()) savedPath = os.getcwd() os.chdir(topdir) runcmd(baseline[4:]) os.chdir(savedPath) elif re.search(r"\.tar\.gz", baseline.lower()) or re.search(r"\.tar", baseline.lower()): try: topdir = tempfile.mkdtemp(dir=os.getcwd()) tar = tarfile.open(baseline) tar.extractall(path=topdir) tar.close() except: log("ERROR", "Invalid or unsupported tar file format for baseline: {0}".format(baseline)) elif re.search("\.zip", baseline.lower()): try: topdir = tempfile.mkdtemp(dir=os.getcwd()) zip = zipfile.ZipFile(baseline) for member in zip.namelist(): if member.endswith("/"): os.makedirs(os.path.join(topdir, member)) else: zip.extract(member, topdir) zip.close() except: log("ERROR", "Invalid or unsupported zip file format for baseline: {0}".format(baseline)) else: orig_topdir = True # optionally remove top level dir if 'RMTOP' in options_list: if orig_topdir: topdir = tempfile.mkdtemp(dir=os.getcwd()) preserve_links = False if 'SYMLINKS' in options_list: preserve_links = True shutil.copytree(baseline, topdir, symlinks=preserve_links) log("DEBUG", "Checking removing top level baseline directory") entries = os.listdir(topdir) if len(entries) > 1: log("ERROR", "RMTOP option can only be used if the top level directory contains one folder.") ignore_dir = os.path.join(topdir, entries[0]) p4_temp_dir = "{0}.tmp".format(topdir) os.rename(ignore_dir, p4_temp_dir) os.rmdir(topdir) os.rename(p4_temp_dir, topdir) # get file name (and md5) for each file in baseline fmap = {} # optionally check for empty directories if 'EMPTYDIRS' in options_list: log("DEBUG", "Checking for empty directories") for root, dirs, names in os.walk(topdir): if len(dirs)==0 and len(names)==0: fname = os.path.join(root, "placeholder.txt")[len(topdir)+1:] fname = fname.replace("\\","/") if 'ALLOWWILDCARDS' in options_list: fname = fname.replace("%","%25") fname = fname.replace("*","%2A") fname = fname.replace("#","%23") fname = fname.replace("@","%40") elif re.match(r"%|\*|#|@", fname): continue if mode == "1": fmap[fname] = 0 else: placeholder_fh, placeholder_name = tempfile.mkstemp() placeholder = os.fdopen(placeholder_fh, "w") placeholder.write("Placeholder\n") placeholder.close() fmap[fname] = md5_for_file(placeholder_name).lower() os.unlink(placeholder_name) preserve_links = True if 'SYMLINKS' in options_list: preserve_links = False for root, dirs, names in os.walk(topdir, followlinks=preserve_links): if 'SYMLINKS' in options_list: for dirname in dirs: adir = os.path.join(root, dirname) if os.path.islink(adir): fname = adir[len(topdir)+1:] fname = fname.replace("\\","/") if 'ALLOWWILDCARDS' in options_list: fname = fname.replace("%","%25") fname = fname.replace("*","%2A") fname = fname.replace("#","%23") fname = fname.replace("@","%40") elif re.match(r"%|\*|#|@", fname): continue if mode == "1": fmap[fname] = 0 else: fmap[fname] = os.readlink(adir).strip() for name in names: thefile = os.path.join(root,name) fname = thefile[len(topdir)+1:] fname = fname.replace("\\","/") if 'ALLOWWILDCARDS' in options_list: fname = fname.replace("%","%25") fname = fname.replace("*","%2A") fname = fname.replace("#","%23") fname = fname.replace("@","%40") elif re.match(r"%|\*|#|@", fname): continue if mode == "1": fmap[fname] = 0 else: if 'SYMLINKS' in options_list and os.path.islink(thefile): fmap[fname] = os.readlink(thefile).strip() else: fmap[fname] = md5_for_file(thefile).lower() if baseline.startswith("CMD:"): rmtree(topdir) elif re.search(r"\.tar\.gz", baseline.lower()) or re.search(r"\.tar", baseline.lower()): rmtree(topdir) elif re.search("\.zip", baseline.lower()): rmtree(topdir) elif (orig_topdir and 'RMTOP' in options_list): rmtree(topdir) if removejunk: fmap_rjd = {} for key in iter(fmap): notjunk = True if re.search(r"\.hgignore$", key): notjunk = False if re.search(r"\.hgtags$", key): notjunk = False if re.search(r"\.cvsignore$", key): notjunk = False if re.search(r"/\.hg/", key) or key.find(".hg/") == 0: notjunk = False if re.search(r"/\.git/", key) or key.find(".git/") == 0: notjunk = False if re.search(r"/\.svn/", key) or key.find(".svn/") == 0: notjunk = False if re.search(r"/CVS/", key) or key.find("CVS/") == 0: notjunk = False if notjunk: fmap_rjd[key] = fmap[key] fmap = fmap_rjd # find changelist for this command change = get_changelist(p4dir, desc) if len(change) < 1: log("ERROR", "Unable to find changelist that produced this command. Searched for description {0}".format(desc)) # get file list from Perforce pmap = {} if mode == "1": fstat = runp4cmd("fstat -T \"depotFile,headAction\" {0}@{1}".format(p4dir, change)) index = fstat.find("... depotFile") while index != -1: endindex = fstat.find("... depotFile", index+1) chunk = '' if endindex != -1: chunk = fstat[index:endindex].strip() else: chunk = fstat[index:].strip() pname = '' paction = '' for c in chunk.splitlines(): m = re.match(r"^\.\.\. depotFile (.+?)$", c) if m != None: pname = m.group(1) pname = pname[len(p4dir)-3:] m = re.match(r"^\.\.\. headAction (.+?)$", c) if m != None: paction = m.group(1) if len(paction) > 0 and paction.find("delete") == -1: pmap[pname] = 0 index = endindex else: fstat = runp4cmd("fstat -Ol -T \"depotFile,digest,headAction,headType\" {0}@{1}".format(p4dir, change)) index = fstat.find("... depotFile") while index != -1: endindex = fstat.find("... depotFile", index+1) chunk = '' if endindex != -1: chunk = fstat[index:endindex].strip() else: chunk = fstat[index:].strip() pname = '' depotname = '' pdigest = '' paction = '' ptype = '' for c in chunk.splitlines(): m = re.match(r"^\.\.\. depotFile (.+?)$", c) if m != None: pname = m.group(1) depotname = pname pname = pname[len(p4dir)-3:] m = re.match(r"^\.\.\. digest (.+?)$", c) if m != None: pdigest = m.group(1) m = re.match(r"^\.\.\. headAction (.+?)$", c) if m != None: paction = m.group(1) m = re.match(r"^\.\.\. headType (.+?)$", c) if m != None: ptype = m.group(1) if len(paction) > 0 and paction.find("delete") == -1: if 'SYMLINKS' in options_list and ptype == 'symlink': pmap[pname] = runp4cmd("print -q \"{0}\"".format(depotname)).strip() else: pmap[pname] = pdigest.lower() index = endindex # find diffs set_fmap = set(iter(fmap)) set_pmap = set(iter(pmap)) fmap_only = set_fmap - set_pmap for d in fmap_only: log("INFO", "\tDIFF Only in baseline {0}: {1} ({2})".format(baseline, d, change)) pmap_only = set_pmap - set_fmap if 'NODELETE' not in options_list: for d in pmap_only: log("INFO", "\tDIFF Only in {0}: {1} ({2})".format(p4dir, d, change)) if mode != "1": common = set_fmap & set_pmap for key in common: if fmap[key] != pmap[key]: log("INFO", "\tDIFF Digests differ for {0}".format(key)) if 'NODELETE' in options_list: changedesc = runp4cmd("describe -s {0}".format(change)) if re.search(r"#\d+ delete$", changedesc, re.M): log("INFO", "\tDIFF Files were deleted in changelist {0}".format(change)) def p4branch(cmd, srcdir, targdir, desc, options=None): # find changelist for this command change = get_changelist(targdir, desc) if len(change) < 1: log("ERROR", "Unable to find changelist that produced this command. Searched for description {0}".format(desc)) options_list = [] branchspec = None if options != None: options_list = options.split(",") for o in options_list: m = re.match(r"BRANCHSPEC=(.+)", o) if m != None: branchspec = m.group(1) if cmd == "BRANCH": # Note that only metadata validation is done for branches. srcfiles = runp4cmd("files {0}@{1}".format(srcdir, change)).strip().split("\n") tgtfiles = runp4cmd("files {0}@{1}".format(targdir, change)).strip().split("\n") for tgtfile in tgtfiles: (depotpath,action,ftype) = re.match(r"^([^#]+)#\d+ - (\S+) change \d+ \((\S+)\)", tgtfile).groups() if action != "branch": log("INFO", "\tDIFF Invalid branch action for {0}".format(tgtfile)) continue flog = runp4cmd("filelog -m1 {0}@{1}".format(depotpath, change)).strip().split("\n") if len(flog) != 3: log("INFO", "\tDIFF Invalid filelog for {0}".format(tgtfile)) continue (action, srcfile) = re.match(r"^\.\.\. \.\.\. ([^/]+) ([^#]+)#", flog[2]).groups() if action != "branch from": log("INFO", "\tDIFF Invalid branch action for {0}".format(srcfile)) continue found = False expected_line = "{0}#".format(srcfile) for possible in srcfiles: if possible.find(expected_line) == 0: srcfiles.remove(possible) found = True break if not found: log("INFO", "\tDIFF Could not find branch pair for {0}".format(tgtfile)) for remaining in srcfiles: log("INFO", "\tDIFF Could not find branch pair for {0}".format(remaining)) if branchspec != None: bform = runp4cmd("branch -o {0}".format(branchspec)) if bform.find(desc) == -1: log("INFO", "\tDIFF Unable to find branch spec {0} with description {1}".format(branchspec, desc)) elif cmd == "COPY_MERGE": # Note that only metadata validation is done for this operation. srcfiles = runp4cmd("files {0}@{1}".format(srcdir, change)).strip().split("\n") tgtfiles = runp4cmd("files {0}@{1}".format(targdir, change)).strip().split("\n") for tgtfile in tgtfiles: (depotpath,action,ftype) = re.match(r"^([^#]+)#\d+ - (\S+) change \d+ \((\S+)\)", tgtfile).groups() if action != "integrate": # because an ignore merge can still introduce new files, # we only check the ones that were integrated continue flog = runp4cmd("filelog -m1 {0}@{1}".format(depotpath, change)).strip().split("\n") if len(flog) != 3: log("INFO", "\tDIFF Invalid filelog for {0}".format(tgtfile)) continue (action, srcfile) = re.match(r"^\.\.\. \.\.\. ([^/]+) ([^#]+)#", flog[2]).groups() if action != "copy from": log("INFO", "\tDIFF Invalid resolve action for {0}".format(srcfile)) continue found = False expected_line = "{0}#".format(srcfile) for possible in srcfiles: if possible.find(expected_line) == 0: found = True break if not found: log("INFO", "\tDIFF Could not find integ pair for {0}".format(tgtfile)) if 'FULLCOPY' in options_list: log("DEBUG", "Checking diff driven merge for COPY_MERGE") ddmchange = get_changelist(targdir, "DIFF_DRIVEN_MERGE {0}".format(desc)) if len(ddmchange) < 1: log("ERROR", "Unable to find changelist for diff driven merge for {0}".format(targdir)) diff2out = runp4cmd("diff2 -q -dw -t {0}@{1} {2}@{3}".format(srcdir, ddmchange, targdir, ddmchange)) if re.search(r"no differing files\.$", diff2out, re.M) or len(diff2out.strip()) < 1: log("DEBUG", "Diff driven merge: no diffs for {0}".format(targdir)) else: for difffile in diff2out.split("\n"): log("INFO", "\tDIFF {0} ({1})".format(difffile, ddmchange)) elif cmd == "RECORD_AS_MERGED": # Note that only metadata validation is done for this operation. srcfiles = runp4cmd("files {0}@{1}".format(srcdir, change)).strip().split("\n") tgtfiles = runp4cmd("files {0}@{1}".format(targdir, change)).strip().split("\n") for tgtfile in tgtfiles: (depotpath,action,ftype) = re.match(r"^([^#]+)#\d+ - (\S+) change \d+ \((\S+)\)", tgtfile).groups() if action != "integrate": # because an ignore merge can still introduce new files, # we only check the ones that were integrated continue flog = runp4cmd("filelog -m1 {0}@{1}".format(depotpath, change)).strip().split("\n") if len(flog) != 3: log("INFO", "\tDIFF Invalid filelog for {0}".format(tgtfile)) continue (action, srcfile) = re.match(r"^\.\.\. \.\.\. ([^/]+) ([^#]+)#", flog[2]).groups() if action != "ignored": log("INFO", "\tDIFF Invalid resolve action for {0}".format(srcfile)) continue found = False expected_line = "{0}#".format(srcfile) for possible in srcfiles: if possible.find(expected_line) == 0: found = True break if not found: log("INFO", "\tDIFF Could not find integ pair for {0}".format(tgtfile)) elif cmd == "RENAME": # Note that only metadata validation is done for renames. srcfiles = runp4cmd("files {0}@{1}".format(srcdir, change)).strip().split("\n") tgtfiles = runp4cmd("files {0}@{1}".format(targdir, change)).strip().split("\n") for tgtfile in tgtfiles: (depotpath,action,ftype) = re.match(r"^([^#]+)#\d+ - (\S+) change \d+ \((\S+)\)", tgtfile).groups() if action != "move/add": log("INFO", "\tDIFF Invalid rename action for {0}".format(tgtfile)) continue flog = runp4cmd("filelog -m1 {0}@{1}".format(depotpath, change)).strip().split("\n") if len(flog) != 3: log("INFO", "\tDIFF Invalid filelog for {0}".format(tgtfile)) continue (action, srcfile, srcver) = re.match(r"^\.\.\. \.\.\. ([^/]+) ([^#]+)#(\d+)", flog[2]).groups() srcver_i = int(srcver) + 1 if action != "moved from": log("INFO", "\tDIFF Invalid rename action for {0}".format(srcfile)) continue expected_line = "{0}#{1} - move/delete change {2} ({3})".format(srcfile,srcver_i,change,ftype) if expected_line in srcfiles: srcfiles.remove(expected_line) else: log("INFO", "\tDIFF Could not find rename pair for {0}: expected {1}".format(tgtfile,expected_line)) for remaining in srcfiles: log("INFO", "\tDIFF Could not find rename pair for {0}".format(remaining)) else: log("ERROR", "Invalid command: {0}".format(cmd)) if __name__ == "__main__": parseoptions() loadp4config() p4 = "{0} -p {1} -c {2} -u {3} ".format(p4, P4PORT, P4CLIENT, P4USER) baselines, commands = loadbbiconfig() processcommands( baselines, commands) log("INFO", "\n\nValidations complete.") sys.exit(0)