#!/usr/bin/env python ################################################################################ # # Copyright (c) 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. # # DATE # # $Date: 2017/06/02 $ # # SYNOPSIS # # IntegHistory.py [-p p4port] [-u p4user] [-c p4client] depotFile[revRange] # * depotFile must be in depot syntax # * depotFile with spaces requires quote # * wildcards and revision specifiers are supported # # DESCRIPTION # # This script collects the full integration history of files. # The result is stored in a gzipped file (IntegHistory.log.gz) that can # be used by IntegHistoryRebuild.py to rebuild a partial Helix server. # ################################################################################ from __future__ import print_function import subprocess from subprocess import Popen, PIPE, STDOUT import argparse import sys import re import gzip import shutil import os class IntegHistory: def __init__(self, depotFiles, logName): self.depotFiles = depotFiles self.logName = logName self.caseHandling = "insensitive" self.isSuper = False self.files = [] self.integed = [] self.changes = [] try: self.log = open(logName, "w") except IOError: sys.exit("Error: can\'t create " + logName) try: self.log.write("['files', '") for i in range(len(self.depotFiles)): self.log.write(self.depotFiles[i]) if i+1 < len(self.depotFiles): self.log.write("', '") self.log.write("']\n") self.log.write("['language', '" + "Python " + ".".join(map(str, sys.version_info[:3])) + "']\n") except IOError: sys.exit("Error: can\'t write in " + logName) def p4Run(self, cmd, toLog = True): tag = False if re.search('-ztag', cmd): tag = True result = [cmd] process = Popen(cmd, shell = True, stdout = PIPE, stderr = STDOUT, universal_newlines = True) (output, err) = process.communicate() exitCode = process.wait() if exitCode == 0: for item in output.splitlines(): if len(item) > 0: if not tag or re.search('^\.\.\. ', item): result.append(item.strip()) else: result[-1] = result[-1] + "\n" + item.strip() else: result.append(output) if toLog == True: self.log.write(str(result) + "\n") return(result) def getConfigData(self): for line in self.p4Run('p4 -ztag info'): match = re.search('\.\.\. caseHandling (.*)', line.strip()) if match: self.caseHandling = match.group(1) break self.p4Run('p4 set') self.p4Run('p4 -ztag user -o') self.p4Run('p4 -ztag client -o') if '... perm super' in self.p4Run('p4 -ztag protects'): self.isSuper = True self.p4Run('p4 -ztag configure show') self.p4Run('p4 counters -e change') self.p4Run('p4 counters -e traits') def getFilesHistory(self, depotFile): if (self.caseHandling == "insensitive" and depotFile.lower() not in map(lambda name: name.lower(), self.files)) or \ (self.caseHandling == "sensitive" and depotFile not in self.files): filelog = self.p4Run('p4 filelog -1 -l -t "' + depotFile + '"', False) if len(filelog) > 2: self.files.append(depotFile) for line in filelog: match = re.search('\.\.\. .* change (\d+)', line.strip()) if match: if not match.group(1) in self.changes: self.changes.append(match.group(1)) else: match = re.search('\.\.\. \.\.\. .* (\/\/.*?)#', line.strip()) if match: self.getFilesHistory(match.group(1)) def getDepotType(self, depot): type = "" for line in depot: match = re.search('\.\.\. Type (.*)', line.strip()) if match: type = match.group(1) break return(type) def getStreamsData(self, streams, stream): if stream not in streams and not stream == "none": result = self.p4Run('p4 -ztag streams "' + stream + '"', False) if not "no such stream" in str(result): result = self.p4Run('p4 -ztag stream -o "' + stream + '"') self.p4Run('p4 -ztag istat -s "' + stream + '"') streams.append(stream) for line in result: match = re.search('\.\.\. Parent (.*)', line.strip()) if match: parent = match.group(1) if parent not in streams: self.getStreamsData(streams, parent) break def getFilesData(self): depots = {} streams = [] for file in self.files: for line in self.p4Run('p4 -ztag files "' + file + '"', False): match = re.search('... depotFile (\/\/.*?\/.*?/.*)', line.strip()) if match and match.group(1) not in self.integed: self.p4Run('p4 -ztag integed "' + match.group(1) + '"') self.integed.append(match.group(1)) flags = "-Of -Ol -Oi " if self.isSuper: flags = flags + "-Oc " self.p4Run('p4 fstat ' + flags + '"' + file + '"') match = re.search('\/\/(.*?)/.*', file) if match: depot = match.group(1) if depot not in depots: depots[depot] = self.getDepotType(self.p4Run('p4 -ztag depot -o "' + depot + '"')) if depots[depot] == "stream": match = re.search('(\/\/.*?\/.*?)/.*', file) if match: self.getStreamsData(streams, match.group(1)) def getChangesData(self): for change in self.changes: self.p4Run('p4 -ztag changes -l @=' + change) def getData(self): self.getConfigData() for depotFile in self.depotFiles: self.getFilesHistory(depotFile) if len(self.files) == 0: sys.exit("Error: no file found, check " + self.logName) self.getFilesData() self.getChangesData() self.log.close() try: with open(self.logName, 'rb') as uncompressed: with gzip.open(self.logName + ".gz", 'wb') as compressed: shutil.copyfileobj(uncompressed, compressed) os.remove(self.logName) return self.logName + ".gz" except: return self.logName def setP4Env(p4port, p4user, p4client): output = subprocess.check_output(["p4", "set"]) for line in output.splitlines(): match = re.search("P4PORT=(.*?) .*", line) if match: if not p4port: p4port = match.group(1) else: match = re.search("P4USER=(.*?) .*", line) if match: if not p4user: p4user = match.group(1) else: match = re.search("P4CLIENT=(.*?) .*", line) if match: if not p4client: p4client = match.group(1) os.environ["P4CONFIG"] = "" os.environ["P4PORT"] = p4port os.environ["P4USER"] = p4user os.environ["P4CLIENT"] = p4client def cmdUsage(): sys.exit('Usage: -p p4port -u p4user -c p4client depotfile[revRange] ...') def main(): parser = argparse.ArgumentParser(prog='IntegHistory', usage='%(prog)s -[-p p4port] [-u p4user] [-c p4client] depotFile[revRange] ...') parser.add_argument("depotFiles", nargs="*", help="log or compressed log") parser.add_argument("-p", "--p4port", dest="p4port", help="server value") parser.add_argument("-u", "--p4user", dest="p4user", help="user value") parser.add_argument("-c", "--p4client", dest="p4client", help="client value") args = parser.parse_args() for depotFile in args.depotFiles: if not re.search('\/\/.*', depotFile): cmdUsage() if args.p4port or args.p4user or args.p4client: setP4Env(args.p4port, args.p4user, args.p4client) logName = os.path.abspath(os.getcwd() + "/IntegHistory.log") history = IntegHistory(args.depotFiles, logName) print("Info: " + history.getData() + " created successfully") if __name__ == '__main__': main()