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.