#!/usr/bin/python2.2 #^--- change the above line to point to your installation of Python # $Id: //guest/mitch_stuart/perforce/utils/delta/p4History.py#1 $ USAGE_MESSAGE = """ Usage: p4History.py p4History.py -help """ HELP_MESSAGE = """ INTRODUCTION Perforce has commands that give the history of changes to a file, and other commands that give information on labels. However, there is no single command that gives an "integrated" history, showing all of its changes and which labels match up with those changes. Having an integrated history is essential for understanding the changes that occurred between labels (that is, the changes between builds, releases, or other milestones that are labeled). This script displays the revision history of a file in reverse chronological order (like p4 changes or p4 filelog), but it also interleaves the labels (if any) applied to this file. Normally you would have to invoke the p4 labels and p4 files commands separately to get all of this information. COMMAND LINE SYNTAX p4History.py EXAMPLE Sample invocation: p4History.py build.sh Sample output: Label ducati-label-5: niranjan: 07/03/03 16:34: Created by niranjan. Label suzuki-label-3: niranjan: 07/02/03 22:19: Created by niranjan. Change 3913: mstuart: 07/01/03 16:03: Split StringMapper utility class into its own file Change 3903: mstuart: 06/30/03 15:59: Implement migration from condenser.xml into database Change 3890: krishna: 06/30/03 11:10: fixed a security bug w/ jboss Change 3886: mstuart: 06/27/03 18:35: Compile JPull and JConsole with the same javac command, because they depend on each other. Change 3863: krishna: 06/26/03 16:39: Added node type detection via node manager Label suzuki-label-2: niranjan: 06/25/03 17:50: Created by niranjan. Change 3851: krishna: 06/25/03 16:49: Build error with cp Change 3823: krishna: 06/24/03 16:19: Product Separation project : another batch of changes Change 3780: mstuart: 06/20/03 19:56: Add MBean meta info class . . . From the above output we can see that Change 3851 is included in suzuki-label-2, but Change 3863 is not. This type of view helps to answer questions about which features and bug fixes appear in which releases. ENVIRONMENT REQUIREMENTS This script invokes the Perforce p4 command-line interface to get the needed information from Perforce. The p4 command must be available on your PATH, and you must have configured your environment so that the p4 command is able to access the Perforce server. """ import marshal import os import sys import time import p4Util def displayFileHistory(filename): # changes is a list of dictionaries, where each dictionary holds the # information about one changelist. changes = p4Util.execP4Marshal("changes -i -l " + filename) # labels is a list of dictionaries, where each dictionary holds the # information about one label. labels = p4Util.execP4Marshal("labels " + filename) # labelsByName holds the same dictionaries as the labels list, but instead # of just being a list, it is a dictionary indexed by label name, # allowing us to look up the entries. labelsByName = {} for label in labels: labelsByName[label["label"]] = label # filesInLabels is a list of dictionaries, where each dictionary holds # the information about the revision of the file (changelist number) # that is in the label. The order of the entries in the filesInLabels # list is the same as the order of the labels list. filesInLabels = [] # labelsByChange is a dictionary indexed by changelist number, where each # value is a list of labels that apply to that changelist number # for the current file. labelsByChange = {} # We only need to execute the "which files are in label" command if we # have found one or more labels. if len(labels) > 0: # Create a command of the form "files myfile@label1 myfile@label2" etc. getFilesInLabelCmd = "files " for label in labels: getFilesInLabelCmd = "%s %s@%s" % (getFilesInLabelCmd, filename, label["label"]) filesInLabels = p4Util.execP4Marshal(getFilesInLabelCmd) # Create a mapping between each changelist number and the labels # (if any) that it belongs to. labelsByChange = mapChangesToLabels(labels, filesInLabels) # We are going to emit the file revisions in reverse chronological # order. For each file revision (changelist number), we first output # any label(s) that apply to that revision, and then the revision # information. for change in changes: # Emit any labels that apply to this changelist number. emitLabelsForChange(labelsByName, labelsByChange, change["change"]) # Emit the change description. emitChange(change) def mapChangesToLabels(labels, filesInLabels): # For each label that is defined for this file, there is exactly one # changelist number (i.e., version of the file) that corresponds to # that label. # # In contrast, for each changelist number (version of the file), that # version of the file may correspond to several labels. For example, if a # file does not change for several releases, then each release label points # to the same version of the file. We do know that there is AT LEAST one # label corresponding to each changelist number, because otherwise the # label would not be in our list of labels applied to this file. # # This function creates a mapping between each changelist number and # the ONE OR MORE labels that contain that version of the file. labelsByChange = {} labelnum = 0 for label in labels: changenum = filesInLabels[labelnum]["change"] labelsForThisChange = [] if labelsByChange.has_key(changenum): # We have already recorded at least one label for this # changelist number, get the list of labels recorded so far. labelsForThisChange = labelsByChange[changenum] else: # We have not recorded any labels for this changelist number # yet, create a new empty list to hold the labels. labelsForThisChange = [] labelsByChange[changenum] = labelsForThisChange # Add this label to the list of labels for this changelist. labelsForThisChange.append(label) labelnum = labelnum + 1 return labelsByChange def emitLabelsForChange(labelsByName, labelsByChange, changenum): if labelsByChange.has_key(changenum): labels = labelsByChange[changenum] # If there are several labels that apply to this change, we want # to display them in reverse chronological order, so sort the list. if len(labels) > 1: labels.sort(p4Util.cmpLabelTimestampReverse) for label in labels: print "Label %s: %s: %s: %s" % (label["label"], label["Owner"], p4Util.getDisplayDateString(label["Update"]), label["Description"].strip()) def emitChange(change): print "Change %s: %s: %s: %s" % (change["change"], change["user"], p4Util.getDisplayDateString(change["time"]), change["desc"].strip()) # ---------- main if len(sys.argv) > 1 and sys.argv[1] == "-help": print HELP_MESSAGE sys.exit(0) if len(sys.argv) < 2: sys.exit(USAGE_MESSAGE) displayFileHistory(sys.argv[1])