""" Copyright (c) Perforce Software, Inc., 2011. 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. User contributed content on the Perforce Public Depot is not supported by Perforce, although it may be supported by its author. This applies to all contributions even those submitted by Perforce employees. """ """ This script attempts to walk the stream hierarchy to report on opened files in all streams. Given a starting stream (from the current workspace) and a file given in any syntax from the current workspace, the script will walk through all sibling streams and see where the file is opened. It uses the stream view to take into account any exclusions or other factors that may prevent the file from being visible in a sibling stream. The script checks to see if the file has been renamed in sibling streams. The name is slightly misleading, as the script cannot lock files not in the current workspace. Usage: megalock.py Requires: P4Python 2011.1+ Python 2.7 Limitations: Further extensive testing required, particularly for handling renames and complex stream views. Future enhancements: 1. Generalize to handle an entire tree or path rather than a single file. This would be a precondition to having the script function effectively as a broker filter. I think the only big problem here is detecting any renames across an entire path intelligently and efficiently. 2. Have the script run as a broker pseudo-command. That would make it more generally available to users. The script would have to be modified to derive the current stream from the client as the broker knows about it. It would also need to configure its environment based on broker parameters. 3. Have the script serve as the basis for a broker filter script that would prevent opens (edit/move/delete/integ) if the file is open in a sibling stream. The script would have to work on only specific file types (+l) or else only detect files that are locked. 4. Detect whether previous changes on sibling streams have been merge already. In other words, make sure that the changes in this stream can be successfully pushed to other streams. """ # imports import sys, types from P4 import P4, P4Exception, Map # our one global, the p4 object p4 = P4() """ init: set up P4 connection and program name """ def init(): p4.exception_level = 2 p4.prog = "Megalock" try: p4.connect() except P4Exception: # If we cannot connect to the server, that # is a client program error. We do not get # server error or warning messages, so we # go ahead and exit with a message. print "Cannot connect to the server." sys.exit(1) """ Run a P4 command and check for errors arg: cl (command and arguments as list) return: command results """ def getdata(cl): try: res = p4.run(cl) return res except P4Exception: for e in p4.errors: print "Error: " + e sys.exit(1) for w in p4.warnings: print "Warning: " + w """ Print the result of any P4 command, mainly for debugging. arg: r - output from a p4.run call """ def printdata(r): # A quick check to be sure we actually have # data to process: if not (isinstance(r, types.NoneType)): for i in range(len(r)): if (p4.tagged): dict = r[i] for key in dict.keys(): print "%s :: %s" % (key,dict[key]) else: print r[i] """ Recursively trace through move records to find the current name of a file. arg: depot_path - original depot path return: current depot path """ def find_renames(depot_path): integ_data = getdata(["integrated", depot_path]) if len(integ_data) > 0: last_integ = integ_data.pop() if last_integ["how"] == 'moved into': return find_renames(last_integ["fromFile"]) else: return depot_path else: return depot_path """ Recursively walk the stream hierarchy to detect opened files in sibling streams. arg: active_stream - current stream arg: depot_file - current depot file path arg: already_checked - streams already walked """ def walk_stream(active_stream, depot_file, already_checked): already_checked[active_stream] = 1 active_stream_spec = p4.fetch_stream(active_stream) # check parent stream first # only if has parent we haven't checked yet parent_stream = active_stream_spec["Parent"] if parent_stream != 'none' and parent_stream not in already_checked: # get temporary branch mapping branchdata = getdata(["branch", "-S", active_stream, "-o", "tmp"]) #printdata(branchdata) branchmap = Map(branchdata[0]["View"]) #print branchmap.as_array() # get depot path in other stream other_path = branchmap.translate(depot_file) if other_path == None: print "{0:<30}{1:<30}".format(parent_stream, 'no mapping') else: #print "Other file is {0}".format(other_path) other_path = find_renames(other_path) opened_data = getdata(["opened", "-a", other_path]) #printdata(opened_data) if len(opened_data) > 0: for i in range(len(opened_data)): opened_entry = opened_data[i] print "{0:<30}{1:<30}{2}".format(parent_stream, opened_entry["user"] + '@' + opened_entry["client"], other_path) else: print "{0:<30}{1:<30}{2}".format(parent_stream, 'not opened anywhere', other_path) walk_stream(parent_stream, other_path, already_checked) # now check children childdata = getdata(["streams", "-F", "Parent=" + active_stream + ""]) for i in range(len(childdata)): child_stream_spec = childdata[i] child_stream = child_stream_spec["Stream"] if child_stream in already_checked: continue # get temporary branch mapping branchdata = getdata(["branch", "-S", child_stream, "-o", "tmp"]) #printdata(branchdata) branchmap = Map(branchdata[0]["View"]) #print branchmap.as_array() # get depot path in other stream other_path = branchmap.translate(depot_file, False) if other_path == None: print "{0:<30}{1:<30}".format(child_stream, 'no mapping') else: #print "Other file is {0}".format(other_path) other_path = find_renames(other_path) opened_data = getdata(["opened", "-a", other_path]) #printdata(opened_data) if len(opened_data) > 0: for i in range(len(opened_data)): opened_entry = opened_data[i] print "{0:<30}{1:<30}{2}".format(child_stream, opened_entry["user"] + '@' + opened_entry["client"], other_path) else: print "{0:<30}{1:<30}{2}".format(child_stream, 'not opened anywhere', other_path) walk_stream(child_stream, other_path, already_checked) """ MAIN PROGRAM CALLS """ init() # get stream name clientspec = p4.fetch_client() if "Stream" not in clientspec: print "Not working in a stream workspace..." sys.exit(1) active_stream = clientspec["Stream"] print "{0:<20}{1:<50}".format('Working in stream ', active_stream) # get file name (first and only arg) if len(sys.argv) != 2: print "Usage: megalock.py " sys.exit(1) active_file = sys.argv[1] wheredata = getdata(["where", active_file]) #printdata(wheredata) depot_file = wheredata[0]["depotFile"] print "{0:<20}{1:<100}".format('Working on file ', depot_file) # walk the stream hierarchy to find all opened instances of this file # start by checking the active stream print "{0:<30}{1:<30}{2}".format('STREAM', 'OPENED WHERE', 'FILE') opened_data = getdata(["opened", "-a", depot_file]) if len(opened_data) > 0: for i in range(len(opened_data)): opened_entry = opened_data[i] print "{0:<30}{1:<30}{2}".format(active_stream, opened_entry["user"] + '@' + opened_entry["client"], depot_file) else: print "{0:<30}{1:<30}{2}".format(active_stream, 'not opened anywhere', depot_file) already_checked = {} walk_stream(active_stream, depot_file, already_checked) # Terminate instance of p4 cleanly: p4.disconnect()