#!/usr/bin/env python ################################################################################ # # Copyright (c) 2017, 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/09 $ # # 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 from collections import OrderedDict 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.depots = [] self.streams = [] self.changes = {} self.depotRE = re.compile('\/\/(.*?)/.*') self.filelog1RE = re.compile('\.\.\. #(\d+) change (\d+)') self.filelog2RE = re.compile('\.\.\. \.\.\. .* (\/\/.*?)#') 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 runCmdFiltered(self, cmd): process = Popen(cmd, shell = True, stdout = PIPE, stderr = STDOUT, universal_newlines = True) (output, err) = process.communicate() exitCode = process.wait() return(list(output.splitlines())) def runCmd(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.runCmd('p4 -ztag info'): match = re.search('\.\.\. caseHandling (.*)', line.strip()) if match: self.caseHandling = match.group(1) break self.runCmd('p4 set') self.runCmd('p4 -ztag user -o') self.runCmd('p4 -ztag client -o') if '... perm super' in self.runCmd('p4 -ztag protects'): self.isSuper = True self.runCmd('p4 -ztag configure show') self.runCmd('p4 counters -e change') self.runCmd('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.runCmd('p4 filelog -1 -l -t "' + depotFile + '"', False) if len(filelog) > 2: self.files.append(depotFile) match = self.depotRE.match(depotFile) if match: depot = match.group(1) if depot not in self.depots: self.depots.append(depot) streamDepth = self.runCmdFiltered('p4 -ztag -F%StreamDepth% depot -o "' + depot + '"') if streamDepth: i = n = 0 stream = "" while n < streamDepth[0].count('/'): i = i + 1 if depotFile[i] == "/": n = n + 1 stream = depotFile[:i] if stream not in self.streams: self.streams.append(stream) for line in filelog: match = self.filelog1RE.match(line.strip()) if match: if not match.group(2) in self.changes: self.changes[match.group(2)] = [] self.changes[match.group(2)].append(depotFile + "#" + match.group(1)) else: match = self.filelog2RE.match(line.strip()) if match: self.getFilesHistory(match.group(1)) def getDepotsData(self): for depot in self.depots: self.runCmd('p4 -ztag depot -o "' + depot + '"') def getStreamsData(self): for stream in self.streams: self.addParentStream(self.runCmdFiltered('p4 -ztag -F %Parent% stream -o "' + stream + '"')[0]) for stream in self.streams: self.runCmd('p4 -ztag stream -o "' + stream + '"') self.runCmd('p4 -ztag istat -s "' + stream + '"') def addParentStream(self, stream): if stream not in self.streams and not stream == "none": self.streams.append(stream) self.addParentStream(self.runCmdFiltered('p4 -ztag -F %Parent% stream -o "' + stream + '"')[0]) def getFilesData(self): flags = "-Ol -Oi" if self.isSuper: flags = flags + " -Oc" for change in self.changes: for depotFile in self.changes[change]: filename = depotFile[0:depotFile.find("#")] self.runCmd('p4 fstat ' + flags + ' "' + filename + '@=' + change + '"') def getIntegedData(self): for change in self.changes: for depotFile in self.changes[change]: filename = depotFile[0:depotFile.find("#")] for depotFile in self.runCmdFiltered('p4 -ztag -F%depotFile% files "' + filename + '@=' + change + '"'): if depotFile not in self.integed: self.runCmd('p4 -ztag integed "' + depotFile + '"') self.integed.append(depotFile) def getChangesData(self): for change in self.changes: self.runCmd('p4 -ztag changes -l @=' + change) def getData(self): self.getConfigData() for depotFile in self.depotFiles: for df in self.runCmdFiltered('p4 -ztag -F%depotFile% files "' + depotFile + '"'): self.getFilesHistory(df) if len(self.files) == 0: sys.exit("Error: No file found, check " + self.logName) self.changes = OrderedDict(sorted(self.changes.items(), key=lambda t: t[0])) self.getDepotsData() self.getStreamsData() self.getFilesData() self.getIntegedData() 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()