import getopt import sys import getopt, sys, string, os, re, time from regsub import gsub,split FatalError = "fatal" from DictMaker import DictMaker from win32pipe import popen from getopt import getopt # # There are several stages to this, but they're all simple: # # 1. Read in (from hard-coded pathname, for now) a list # of codelines, in the form: # source=XX dest=YY # These are expected to be Python regular expressions # of the form "//depot/main/.*" and "//depot/rel1/.*" # 2. Then, for the current changenumber and client (passed # on the command line as "-c chgnum" and "-C clientname", # a. Run the command "p4 opened -c chgnum" to get the list # of what's in the changelist. Only pay attention to # branches and integrates (and 'imports'). # b. For each of those files, run "p4 where" to get its # pathname in the "//depot/blah" and "c:\work\blah" # syntax. # c. Then run "p4 resolved" to see what files were resolved # for this client, and more importantly, what files they # pulled content from. # d. Match that up against the codelines read in step #1, # and refuse any that don't fit the pattern. # # To install, put this into your "p4 triggers" lines: # Triggers: # nobadcodelines //... "python PATHNAME/parse.py -c %changelist% -C %client%" # (Change the "PATHNAME" and the hard-coded 'parse.txt' a few lines # down, first!) # # # Use M. Meyer's "DictMaker" module to make a little easier. # (Basically, we store a compiled regular expression for the # parent and child of each valid combination. We use that later # for comparisons.) # def ReadInIntegrates(codeline_filename = 'c:/p4client/parse.txt'): blessed = [] parse_codelines = DictMaker(r'parent=(.*)\s+child=(.*)') comment_line = DictMaker(r'^\s*$|^\s*#') fd = open(codeline_filename, 'r') for l in fd.readlines(): if comment_line[l] != None: continue res = parse_codelines[string.strip(l)] if res != None: [parent,child] = res blessed.append([DictMaker(parent), DictMaker(child)]) else: print "Ignoring line '%s' in parse.txt" % l fd.close() return blessed def IsAllowed(infile, outfile, Blessed): for [left_re,right_re] in Blessed: if left_re[infile] != None and right_re[outfile] != None: return 1 if right_re[infile] != None and left_re[outfile] != None: return 1 if left_re[infile] != None and left_re[outfile] != None: return 1 # allow renames within a codeline if right_re[infile] != None and right_re[outfile] != None: return 1 # allow renames within a codeline return 0 #------------------------------------------------------------------------- opts, args = getopt(sys.argv[1:], 'C:c:') chgnum = "default" clientname = "" for o, a in opts: if o == '-c': chgnum = a if o == '-C': clientname = a if clientname == "": print "Client name not specified via '-C'" sys.exit(1) #------------------------------------------------------------------------- # Ready to go... #------------------------------------------------------------------------- #-- regular expressions for output of 'p4 opened' and 'p4 resolved' and #-- 'p4 where'. You will probably want to look over the "p4 where" one #-- to make more robust in the face of filenames with embedded spaces. opened_line = DictMaker(r'(.*)#.* - (branch|integrate|import)') resolved_line = DictMaker(r'(.*) - (branch|import|integrate) from (//.*)#.*') where_line = DictMaker(r'(//.*) (//.*) (.*)') Blessed_Integrates = ReadInIntegrates() current_integrates = [] opened = {} #-- #-- get the list of all files opened *on this client for this changelist* #-- fd = popen('p4 -c %s opened -c %s' % (clientname, chgnum), 'r') lines = fd.readlines() for l in lines: open_out = opened_line[l] if open_out != None: [fname, action] = open_out where_fd = popen('p4 -c %s where "%s"' % (clientname, fname), 'r') w = where_line[where_fd.readline()] if w == None: continue # probably should be err else: [depotsyntax, clientsyntax, localsyntax] = w opened[localsyntax] = depotsyntax where_fd.close() fd.close() #-- at this point, for any [interesting] opened file, #-- opened["c:\work\blah"] #-- will return its depot name. We'll need that, later - both #-- sides of the equation. #-- #-- get the list of all files that have been resolved/integ'ed/imported #-- on this client. Unfortunately, we cannot restrict to only those #-- on a certain changelist, so we'll join this list with the #-- info from "p4 opened" - if it's on the "opened" list, we'll #-- look at it; if not, it's probably opened on another changelist #-- that we don't care about. #-- fd = popen('p4 -c %s resolved' % clientname, 'r') lines = fd.readlines() for t in lines: res = resolved_line[string.rstrip(t)] if res == None: print "match failed on '%s'" % t else: [localname,action,infile] = res if opened.has_key(localname): current_integrates.append([opened[localname], infile]) fd.close() #-- #-- at this point, "current_integrates" is an ARRAY that contains, #-- in depot syntax, a bunch of lists of the form: #-- ["//depot/newfileIamCreating", "//depot/oldfileIamIntegratingFrom"] #-- This only contains the files we're submitting from this client. #-- #-- So now, we just loop through the list of all the candidates, #-- looking for things we don't want to allow. #-- IsAcceptableChange = 1 for [destfile,srcfile] in current_integrates: if IsAllowed(srcfile, destfile, Blessed_Integrates) == 0: print "Change for %s (from %s) is disallowed." % (destfile, srcfile) IsAcceptableChange = 0 if IsAcceptableChange: sys.exit(0) # Looks okay else: print "Change refused.\nYou are probably integrating from a codeline to some\ncodeline that isn't its direct parent/child." sys.exit(1) # Looks bad, refuse the change.