#! /usr/bin/env python
# $Id: //guest/thomas_quinot/perforce/utils/metrics/excelmetrics.py#1 $
##
## Written by Scott Pasnikowski around the time of 4/22/99
## @symantec corp. Home of the Norton utilities
##
## Under duress of pesky QA people...
##
## This comes with no guarantee whatsoever... on any level.
##
## Consider this to be under the linux type liscense thingy
## ( Don't recall what its called GNU or copyleft or whatever )
## and if anyone complains my manager du jour said I could give it away.
## ( he really did )
##
import sys, os, string, re
# everybody is using this
expFileNameField = re.compile( r'^(//.+)/(.+)' )
# This func corresponds to p4 diff2 with the -dn switch
def extract_metrics_counts_rcs( depot_root, label_one, label_two ):
# Does a p4 changes depot_root@label_one depot_root@label_two
# depot_root is used to limit the diff range to a single "project"
# or something even smaller.. like a single file
# we then parse the output and add up the totals
command = 'p4 diff2 -dn \"' + depot_root + label_one + '\" \"' + depot_root + label_two + '\"'
lines_added = 0
lines_changed = 0
lines_deleted = 0
#sys.stdout.write('Extraction function using ' + command +' \n' )
# break the line up according to...
# sample line: a85 2
# operation (line)location (Number of lines affected)count
p = re.compile( r'^([ad])([0-9]+) (\d+)' )
for line in os.popen( command,'r').readlines():
tmp = line[0:1]
if tmp == 'a' or tmp == 'd':
# only do this if we have a match otherwise groups() will blow chunks diffing an rtf
m = p.match( line )
if m:
(operation, location, count) = re.match( r'^([ad])([0-9]+) (\d+)', line).groups()
#sys.stdout.write( '>' + operation + '< >' + location + '< >' + count + '<\n' )
if tmp == 'a':
lines_added = lines_added + int( count )
elif tmp == 'd':
lines_deleted = lines_deleted + int( count )
return lines_added, lines_deleted
# This func corresponds to p4 diff2 with the -dc switch
def extract_metrics_counts_context( depot_root, label_one, label_two ):
# Does a p4 changes depot_root@label_one depot_root@label_two
# depot_root is used to limit the diff range to a single "project"
# or something even smaller.. like a single file
# we then parse the output and add up the totals
command = 'p4 diff2 -dc \"' + depot_root + label_one + '\" \"' + depot_root + label_two + '\"'
lines_added = 0
lines_changed = 0
lines_deleted = 0
#sys.stdout.write('Extraction function using ' + command +' \n' )
for line in os.popen( command,'r').readlines():
# sample line: Change number on date by name@machine 'comment'
# dummy ch#
#sys.stdout.write( line + '\n' )
tmp = line[0:1]
if tmp == '+':
lines_added = lines_added + 1
elif tmp == '-':
lines_deleted = lines_deleted + 1
elif tmp == '!':
lines_changed = lines_changed +1
return lines_added,lines_changed,lines_deleted
def extract_metrics_counts_summary( depot_root, label_one, label_two ):
# Does a p4 changes depot_root@label_one depot_root@label_two
# depot_root is used to limit the diff range to a single "project"
# or something even smaller.. like a single file
# we then parse the output and add up the totals
command = 'p4 diff2 -ds \"' + depot_root + label_one + '\" \"' + depot_root + label_two + '\"'
lines_added = 0
add_chunks = 0
lines_deleted = 0
delete_chunks = 0
lines_changed = 0
change_chunks = 0
extra_data = 0
#sys.stdout.write('Extraction function using ' + command +' \n' )
# This will match lines starting with
exp1 = re.compile( r'^["add""deleted""changed]"' )
#
# The next 3 eat the add deleted and changed lines
# it could be one expression I suppose
#
exp2 = re.compile( r'^(add) (\d+) (chunks) (\d+)' )
exp3 = re.compile( r'^(deleted) (\d+) (chunks) (\d+)' )
exp4 = re.compile( r'^(changed) (\d+) (chunks) (\d+) / (\d+)' )
for line in os.popen( command,'r').readlines():
# only do this if we have a match
m2 = exp2.search( line )
m3 = exp3.search( line )
m4 = exp4.search( line )
if m2:
( operation, count1, chunks, count2 ) = m2.groups()
add_chunks = add_chunks + int( count1 )
lines_added = lines_added + int( count2 )
#sys.stdout.write( count2 + '\n' )
elif m3:
( operation, count1, chunks, count2 ) = m3.groups()
delete_chunks = delete_chunks + int( count1 )
lines_deleted = lines_deleted + int( count2 )
#sys.stdout.write( count2 + '\n' )
elif m4:
( operation, count1, chunks, count2, count3 ) = m4.groups()
change_chunks = change_chunks + int( count1 )
extra_data = extra_data + int( count2 )
lines_changed = lines_changed + int( count3 )
#sys.stdout.write( count3 + '\n' )
return lines_added,add_chunks,lines_deleted,delete_chunks,lines_changed,change_chunks, extra_data
# This func corresponds to p4 diff2 with the -du switch
def extract_metrics_counts_unified( depot_root, label_one, label_two ):
# Does a p4 changes depot_root@label_one depot_root@label_two
# depot_root is used to limit the diff range to a single "project"
# or something even smaller.. like a single file
# we then parse the output and add up the totals
command = 'p4 diff2 -du \"' + depot_root + label_one + '\" \"' + depot_root + label_two + '\"'
lines_added = 0
lines_deleted = 0
#sys.stdout.write('Extraction function using ' + command +' \n' )
for line in os.popen( command,'r').readlines():
# sample line: Change number on date by name@machine 'comment'
# dummy ch#
#sys.stdout.write( line + '\n' )
tmp = line[0:1]
if tmp == '+':
lines_added = lines_added + 1
elif tmp == '-':
lines_deleted = lines_deleted + 1
return lines_added, lines_deleted
# This func corresponds to p4 diff2 with no switchs
def extract_metrics_counts_flat( depot_root, label_one, label_two ):
# Does a p4 changes depot_root@label_one depot_root@label_two
# depot_root is used to limit the diff range to a single "project"
# or something even smaller.. like a single file
# we then parse the output and add up the totals
command = 'p4 diff2 ' + depot_root + label_one + ' ' + depot_root + label_two
lines_added = 0
lines_deleted = 0
lines_changed = 0
#sys.stdout.write('Extraction function using ' + command +' \n' )
expFile = re.compile( r'^====' )
# The next 4 eat the add deleted and changed lines
# it could be one expression I suppose
#
exp1 = re.compile( r'^(\d+)([acd])(\d+)' )
exp2 = re.compile( r'^(\d+),(\d+)([acd])(\d+)' )
exp3 = re.compile( r'^(\d+)([acd])(\d+),(\d+)' )
exp4 = re.compile( r'^(\d+),(\d+)([acd])(\d+),(\d+)' )
for line in os.popen( command,'r').readlines():
# only do this if we have a match
m1 = exp1.search( line )
m2 = exp2.search( line )
m3 = exp3.search( line )
m4 = exp4.search( line )
if m4:
( count1, count2, operation, count3, count4 ) = m4.groups()
mLine = expFile.match( prev_line )
if mLine:
sys.stdout.write( prev_line + '\n' )
sys.stdout.write( count1 + ' ' + count2 + ' ' + operation + ' ' + count3 + ' ' + count4 + '\n' )
elif m3:
( count1, operation, count2, count3 )= m3.groups()
mLine = expFile.match( prev_line )
if mLine:
sys.stdout.write( prev_line + '\n' )
sys.stdout.write( count1 + ' ' + operation + ' ' + count2 + ' ' + count3 + '\n' )
elif m2:
( count1, count2, operation, count3 ) = m2.groups()
mLine = expFile.match( prev_line )
if mLine:
sys.stdout.write( prev_line + '\n' )
sys.stdout.write( count1 + ' ' + count2 + ' ' + operation + ' ' + count3 + '\n' )
elif m1:
( count1, operation, count2 ) = m1.groups()
mLine = expFile.match( prev_line )
if mLine:
sys.stdout.write( prev_line + '\n' )
sys.stdout.write( count1 + ' ' + operation + ' ' + count2 + '\n' )
prev_line = line
return lines_added, lines_deleted, lines_changed
def extract_metrics_counts_file( depot_root, label_one, label_two ):
# Does a p4 changes depot_root@label_one depot_root@label_two
# depot_root is used to limit the diff range to a single "project"
# or some subset even smaller..
# we then parse the output and add up the totals
# Use quotes around the file params in case someone inputs a path that contains space chars
command = 'p4 diff2 \"' + depot_root + label_one + '\" \"' + depot_root + label_two + '\"'
debug = 0
files_added = 0
files_deleted = 0
files_typechange = 0
files_changed = 0
revisions_added = 0
file_count1 = 0
file_count2 = 0
file_list = []
# fyi
#sys.stdout.write('Start point ' + label_one + ' End Point ' + label_two + '\n' )
#sys.stdout.write('Depot Range ' + depot_root + '\n\n' )
# Only process the lines that list filespec1 - filespec2
expMain = re.compile( r'^====' )
# If the first file is missing it will show up as the following
expMissingFirst = re.compile( r'< none >' )
# If the second file is missing is will show up as ( notice missing spaces)
expMissingSecond = re.compile( r'<none>' )
# special case of no second file
exp1 = re.compile( r' - <' )
# in all other cases this is the separator
exp2 = re.compile( r' - //' )
# We will use the # to split the file spec from the revision number
exp3 = re.compile( r'#' )
spaceExp = re.compile( r' ' )
for line in os.popen( command,'r').readlines():
# only for lines listing file specs
mMain = expMain.search( line )
if mMain:
if debug:
sys.stdout.write( line )
# Preventive cleanup
file1_section1 = file1_section2 = file2_section1 = file2_section2 = ''
file1_revnum = file2_revnum = file1_type =file2_type = delimiter = compare_status = ''
temp = 0
mMissingFirst = expMissingFirst.search( line )
mMissingSecond = expMissingSecond.search( line )
# lets start off thinking that each file has 1 rev then
# later we will break the "real" rev number out of each filespec
# and place it in one of these
#
rev_number1 = 1
rev_number2 = 1
# String splitting for first file
#
if mMissingFirst:
# First file is empty so this is an added file
if debug:
sys.stdout.write( 'Added!!!!\n' )
files_added = files_added + 1
rev_number1 = 0
else:
# break the line up into each of its file parts
# we must check here if we are missing the second filespec because
# then the delimiter changes from "- //' to "- <"
# We cant use plain "-" because it may be in a filename
if mMissingSecond:
( section1, section2 ) = exp1.split( line )
else:
( section1, section2 ) = exp2.split( line )
if debug:
sys.stdout.write( 'section1->' + section1 + '\n' )
sys.stdout.write( 'section2->' + section2 + '\n' )
# main count files in first @label
file_count1 = file_count1 + 1
# now split the path from the rev number
( file1_section1, file1_section2 ) = exp3.split( section1 )
if debug:
sys.stdout.write( 'file1_section1->' + file1_section1 + '\n' )
sys.stdout.write( 'file1_section2->' + file1_section2 + '\n' )
if mMissingSecond:
# If your missing second file there will be no file type to
# split off
rev_number1 = int( file1_section2 )
if debug:
sys.stdout.write( 'file1 rev number->' + file1_section2 + '\n' )
else:
( file1_revnum, file1_type ) = re.split( ' ', file1_section2, 2 )
rev_number1 = int( file1_revnum )
if debug:
sys.stdout.write( 'file1 rev number->' + file1_revnum + '\n' )
sys.stdout.write( 'file1 type->' + file1_type + '\n' )
# String splitting for second file
#
if mMissingSecond:
# Second file is empty so this is a deleted file
files_deleted = files_deleted + 1
rev_number2 = 0
# Restore file name for re search (add //) and print out excel line
#
s1 = expFileNameField.search( '//' + file2_section1 )
if s1:
( depotPath, fileName ) = s1.groups()
sys.stdout.write( depotPath + ',' + file2_section1 + ',Deleted,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n' )
if debug:
sys.stdout.write( 'Deleted!!!!\n' )
else:
# We are here so we dont need to test for second filespec missing
( section1, section2 ) = exp2.split( line )
if debug:
sys.stdout.write( 'section1->' + section1 + '\n' )
sys.stdout.write( 'section2->' + section2 + '\n' )
# main count of files in second @label
file_count2 = file_count2 + 1
( file2_section1, file2_section2 ) = exp3.split( section2 )
if debug:
sys.stdout.write( 'file2_section2->' + file2_section2 + '\n' )
if mMissingFirst:
# This is a file that has been added
#
s1 = expFileNameField.search( '//' + file2_section1 )
if s1:
( depotPath, fileName ) = s1.groups()
sys.stdout.write( depotPath + ',' + fileName + ',Added,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n' )
( file2_revnum, compare_status ) = re.split( ' ', file2_section2, 2 )
if debug:
sys.stdout.write( 'file2 rev number->' + file2_revnum + '\n\n' )
revisions_added = revisions_added + int( file2_revnum )
#sys.stdout.write( 'Added,//' + file2_section1 )
#sys.stdout.write( ',' + `int( file2_revnum)` + '\n' )
else:
( file2_revnum, file2_type, delimiter, compare_status ) = re.split( ' ', file2_section2, 4 )
if debug:
sys.stdout.write( 'file2 rev number->' + file2_revnum + '\n' )
sys.stdout.write( 'file2 type->' + file2_type + '\n' )
sys.stdout.write( 'file compare status->' + compare_status + '\n' )
rev_number2 = int( file2_revnum )
temp = rev_number2 - rev_number1
revisions_added = revisions_added + temp
if rev_number1 != rev_number2:
file_list.append( '//' + file2_section1 )
#sys.stdout.write( '//' + file2_section1 + '\n' )
files_changed = files_changed + 1
else:
s1 = expFileNameField.search( '//' + file2_section1 )
if s1:
( depotPath, fileName ) = s1.groups()
sys.stdout.write( depotPath + ',' + fileName + ',Unchanged,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n' )
if file1_type != file2_type:
files_typechange = files_typechange + 1
return files_added, files_deleted, files_typechange, files_changed, revisions_added, file_count1, file_count2, file_list
#
# This script takes a depot path and two labels (like @jackalope.20 )
# OR two date-time combos (like @1999/02/02 )
#
# The output then gives you the depot path, file name, action type and line change counts
# of files changed deleted or added. You can then feed the output to excel as a comma
# delimited database *.csv and make pretty charts (barf)
#
# Main body of program
#
#
depot_location = sys.argv[1]
diff_label1 = sys.argv[2]
diff_label2 = sys.argv[3]
#sys.stdout.write( 'Starting processing tree data....\n\n' )
#call the diff with the RCS flags
#
#
adds = changes = deletes = 0
( added, deleted, typechange, changed, revs_added, count1, count2, list_of_changed_files ) = \
extract_metrics_counts_file( depot_location, diff_label1, diff_label2 )
for different_file in list_of_changed_files:
#sys.stdout.write( different_file + '\n' )
( rcsadds, rcsdeletes) = \
extract_metrics_counts_rcs( different_file, diff_label1, diff_label2 )
( ctxadds, ctxchanges, ctxdeletes) = \
extract_metrics_counts_context( different_file, diff_label1, diff_label2 )
( sumadds, sumaddchunks, sumdeletes, sumdeletechunks, sumchanges, sumchangechunks, sumextra ) = \
extract_metrics_counts_summary( different_file, diff_label1, diff_label2 )
( uniadds, unideletes) = \
extract_metrics_counts_unified( different_file, diff_label1, diff_label2 )
s1 = expFileNameField.search( different_file )
if s1:
( depotPath, fileName ) = s1.groups()
sys.stdout.write( depotPath + ',' +
fileName + ',' +
'Changed' + ',' +
`rcsadds` + ',' +
`rcsdeletes` + ',' +
`ctxadds` + ',' +
`ctxchanges` + ',' +
`ctxdeletes` + ',' +
`sumadds` + ',' +
`sumaddchunks` + ',' +
`sumdeletes` + ',' +
`sumdeletechunks` + ',' +
`sumchanges` + ',' +
`sumchangechunks` + ',' +
`sumextra` + ',' +
`uniadds` + ',' +
`unideletes` + '\n' )