## $Id$ ## --------- --------- --------- --------- --------- --------- ##文件:cvs_stat.pl ##用途:从CVS LOG文件中统计出代码修改情况 ##作者:honghunter (honghunter@gmail.com honghunter@126.com) ##编写:2006-04-11 14:26 ## --------- --------- --------- --------- --------- --------- #use warnings; use Getopt::Long; use Data::Dumper; use Cwd; $Data::Dumper::Purity = 1; # 为了处理是自参考的变量 my $current_path = getcwd; my $conf_file = "$current_path/cvs_stat.conf"; if (-e $conf_file) { require($conf_file); } else { print "Can not Load [$conf_file],exit.\n"; exit(1); } my $m_info_file = "cvs_stat.perldata" ; my $m_cvs_log_file = "cvs_log.txt" ; if ( $cvslog ne "" ) { $m_cvs_log_file = $cvslog ; } my $m_report_title = "" ; if ( $default_title ne "" ) { $m_report_title = $default_title ; } &getopts(); if (! -e $m_cvs_log_file ) { print "Can not found $m_cvs_log_file , exit. \n"; exit(1); } # ---- build time hash---- my %hash_time ; $hash_time{'begin'} = 0 ; $hash_time{'end'} = 0 ; $hash_time{'init'} = 0 ; # init flag # ---- build time hash--end-- my $DEBUG = 1; print < $server CVS statistics
CVS statistics for $server $m_report_title
EOF $global = 0; # Global total number of added lines $highnum = 0; # High number of revisions $highfile = ""; # Highest revised file $highcommits = 0; # High number of commits per a committer $highcommitter = ""; # Highest revising committer $current_is_binary = 0; # Is the current file a binary? Its +x-y are bogus $skipfile = 0; # Is this a file we want to omit? my %CVS_STAT_RESULT ; # 每天代码行 my $day_index = 0 ; my %working_day; # 统计到的所有发生过CVS提交动作的日期 my %author_hash; # 统计到的所有发起过CVS提交动作的人员名称 my $source_line = "" ; my $temp_change = "" ; my $temp_str = "" ; my $revision_day = "" ; my $work_file_name = "" ; my $changes_add = 0 ; my $changes_dec = 0 ; my $new_file = 0 ; open(DATA, "<$m_cvs_log_file") || die "$!"; while () { while (!/^RCS file:/ && $skipfile) { $_ = ; last unless $_; } if (/^date: /) { $source_line = "$_"; # get author $author = "$_"; $author =~ s/date: .* author: (.*) state: .*/$1/i; $author =~ s/\;//g; # 提取变更日期 $revision_day = $source_line; $revision_day =~ s/date: (.*) author: .* state: .*/$1/i; $revision_day =~ s/\;//g; $revision_day = substr($revision_day,0,10); # print STDERR "revision_day=$revision_day#\n" if $DEBUG ; # 提取filename $work_file_name = $source_line; $work_file_name =~ s/date: .* author: .* state: .* filename: (.*);/$1/i; $work_file_name =~ s/\;//g; $work_file_name = &trim($work_file_name); # print "source_line=$source_line\n"; # print "work_file_name=$work_file_name\n"; #将用户administrator的合并到Administrator的记录中 $temp_str = "administrator" ; $temp_str = lc(substr($author,0,length($temp_str))) ; if ( $temp_str eq "administrator" ) { $author = "Administrator" ; } $author = &trim($author); # 记录作者 if (! exists $author_hash{"$author"} ) { $author_hash{"$author"} = 1 ; } if (! exists $CVS_STAT_RESULT{'index'}{"$revision_day"} ) { $CVS_STAT_RESULT{'index'}{"$revision_day"} = $day_index ; $working_day{$day_index++} = $revision_day ; } if ($lines{"$author"} !~ /./) { $lines{"$author"} = 0; } if ($commits{"$author"} !~ /./) { $commits{"$author"} = 0; } $commits{"$author"} ++; if ( ! exists $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"} ) { $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"all"} = 0 ; $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"add"} = 0 ; $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"dec"} = 0 ; } if (/lines:/ && !$current_is_binary) { $changes = "$_"; $changes =~ s/.*lines: \+([0-9]*) \-([0-9]*)/$1+$2/; $changes_add = $source_line; $changes_add =~ s/.*lines: \+([0-9]*) \-.*/$1/i; $changes_add =~ s/\;//g; $changes_add = &trim($changes_add); $changes_dec = $source_line; $changes_dec =~ s/.*lines: \+.* \-([0-9]*).*/$1/i; $changes_dec =~ s/\;//g; $changes_dec = &trim($changes_dec); # print "\n\n\n$changes_add#$changes_dec\n"; #$abschange = eval "$changes"; $temp_change = substr($changes,0,index($changes,";")) ; # print "\n\n\ntemp_change=$temp_change\n"; $abschange = eval "$temp_change"; # if (!$skipfile) { # print "curfile=$curfile\n"; # print "source_line=$source_line\n"; # print "----------revision_day=$revision_day author=$author abschange=$abschange\n"; # } $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"all"} += $abschange; $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"add"} += $changes_add; $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"dec"} += $changes_dec; $lines{"$author"} += $abschange; $global += $abschange; } else { # 新文件 if ( ! exists $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"new"}{'count'} ) { $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"new"}{'count'} = 0 ; } $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"new"}{'count'} += 1; #$CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"new"}{$CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"new"}{'count'}} = $work_file_name ; $CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"new"}{$CVS_STAT_RESULT{'lines'}{"$revision_day"}{"$author"}{"new"}{'count'}} = $curfile ; } } if (/^RCS file:/) { if ($files{"$curfile"} > $highnum) { $highnum = $files{"$curfile"}; $highfile = "$curfile"; } $curfile = "$_"; $curfile =~ s/RCS file: //; $files{"$curfile"} = 0; # $skipfile = 0; # for (@ignorefiles) { # if ($curfile =~ /$_/) { # $skipfile = 1; # } # } $skipfile = 1; #print STDERR "curfile=$curfile\n"; if ( 1 ) { for my $tmp_file_ext (@check_files) { if ( index("$curfile,v","$tmp_file_ext,v") >= 0 ) { $skipfile = 0; last; } } } $current_is_binary = 0; } if (/^revision/) { $files{"$curfile"} ++; } # # 新文件(只有一个版本) # if (/^total revisions: 1; selected revisions: 1/) { # $new_file = 1; # } # 二进制文件 if (/^keyword substitution: b/) { $current_is_binary = 1; } } close(DATA); $count = 0; my %total; $total{'count'} = 0 ; $total{'number'} = 0 ; $total{'changes'} = 0 ; $total{'average'} = 0 ; $total{'percent'} = 0 ; foreach $committer (sort keys %commits) { $number = $commits{"$committer"}; $changes = $lines{"$committer"}; if (($number > $minlines) && ($changes > $minchanges)) { $percent = ($changes / $global) * 100 * 100; $average = $changes / $number * 100; foreach $_ ($percent, $average) { $_ += .5; s/\..*//g; } if ($number > $highcommits) { $highcommits = $number; $highcommitter = "$committer"; } $percent = $percent / 100; $average = $average / 100; # print <$committer # # # # # #EOF &print_html_table_line("$committer","$number","$changes","$average","$percent\%"); } $total{'count'} += 1; $total{'number'} += $number ; $total{'changes'} += $changes ; $total{'percent'} += $percent ; } $total{'percent'} = $total{'percent'} * 100 ; $total{'average'} = $total{'changes'} / $total{'number'} * 100 ; foreach $_ ($total{'percent'}, $total{'average'}) { $_ += .5; s/\..*//g; } $total{'percent'} = $total{'percent'} / 100 ; $total{'average'} = $total{'average'} / 100 ; if ((($count++) % 2) && ($color_row_2 =~ /./)) { print < EOF } else { print < EOF } print <TOTAL: EOF my $commit_count = $count - 1 ; my $temp_author = ""; my $temp_count = 0 ; foreach $temp_author (sort keys %author_hash) { $CVS_STAT_RESULT{'author'}{"$temp_author"} = $temp_count; $author_hash{"$temp_author"} = $temp_count++; } #open (FILE, "> $m_info_file") or die "can't open [$m_info_file]!"; #print FILE Data::Dumper->Dump([\%CVS_STAT_RESULT], ['*CVS_STAT_RESULT']); #close FILE or die "can't close [$m_info_file]!"; # start test table my $temp_commit_day = 0 ; my $temp_commit_lines = 0 ; my $temp_commit_lines_add = 0 ; my $temp_commit_lines_dec = 0 ; my $temp_commit_lines_new = 0 ; my $show_new_file_max = 5 ; foreach $temp_commit_day (sort values %working_day) { if ( exists $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"} ) { &print_html_table_head("$temp_commit_day","all","+","-","new"); #&print_html_new_table_head("$temp_commit_day","当日修改行数","+","-","当日新增文件"); $count = 0 ; #初始化表格行间隔行底色计数 foreach $temp_author (sort keys %author_hash) { if ( exists $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"all"} ) { $temp_commit_lines = $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"all"}; $temp_commit_lines_add = $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"add"}; $temp_commit_lines_dec = $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"dec"}; if ( exists $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{'count'} ) { #Title中可以使用 来起到换行的作用 #eg.: 13980 $temp_commit_lines_new = ' $show_new_file_max ) { for( my $tmp_new_file_count = 1 ; $tmp_new_file_count <= $show_new_file_max ; $tmp_new_file_count++) { if ( exists $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{$tmp_new_file_count}) { $temp_commit_lines_new = $temp_commit_lines_new . $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{$tmp_new_file_count} . " " ; } } $temp_commit_lines_new = $temp_commit_lines_new . "......" ; } else { for( my $tmp_new_file_count = 1 ; $tmp_new_file_count <= $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{'count'} ; $tmp_new_file_count++) { if ( exists $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{$tmp_new_file_count}) { $temp_commit_lines_new = $temp_commit_lines_new . $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{$tmp_new_file_count} . " " ; } } } $temp_commit_lines_new = $temp_commit_lines_new . '">' . $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{'count'} . "" ; } else { $temp_commit_lines_new = 0 ; } &print_html_table_line("$temp_author","$temp_commit_lines","$temp_commit_lines_add","$temp_commit_lines_dec","$temp_commit_lines_new"); # if (&quick_check_day($temp_commit_day, $stat_date_begin, $stat_date_end, \%hash_time ) == 0 ) { my $temp_n = $CVS_STAT_RESULT{'lines'}{"$temp_commit_day"}{"$temp_author"}{"new"}{'count'} ; &WriteToCSVFile($temp_commit_day, $temp_author, $temp_commit_lines, $temp_commit_lines_add, $temp_commit_lines_dec, $temp_n ); } } else { $temp_commit_lines = 0 ; #skip output } } } } # print html table end print <
Committer \# of Changes Lines Changed Lines per Change \% of Changed Lines
$number$changes$average$percent\%
$total{'number'} $total{'changes'} $total{'average'} $total{'percent'}\%
Total committers: $commit_count
Most frequently modified file: $highfile ($highnum)
Most frequent committer: $highcommitter ($highcommits)
$server CVS statistics generated by cvstat
These statistics are provided purely for interested developers, and are not intended to reflect quality -or- quality of work done by any given developer in CVS, merely to show activity of operations in the CVS repository performed by all developers. If you want to use them to motivate yourself, that's fine, but keep in mind that a bit of useful work is more meaningful than a lot of useless work.
Created by Juli Mallett
EOF ### sub sub print_html_new_table_head() { my ($str_cell_0,$str_cell_1,$str_cell_2,$str_cell_3,$str_cell_4) = @_; $str_cell_0 = " " unless $str_cell_0 ; $str_cell_1 = " " unless $str_cell_1 ; $str_cell_2 = " " unless $str_cell_2 ; $str_cell_3 = " " unless $str_cell_3 ; $str_cell_4 = " " unless $str_cell_4 ; print <
EOF } sub print_html_table_head() { my ($str_cell_0,$str_cell_1,$str_cell_2,$str_cell_3,$str_cell_4) = @_; $str_cell_0 = " " unless $str_cell_0 ; $str_cell_1 = " " unless $str_cell_1 ; $str_cell_2 = " " unless $str_cell_2 ; $str_cell_3 = " " unless $str_cell_3 ; $str_cell_4 = " " unless $str_cell_4 ; # # if ( substr($str_cell_0,8,2) eq "21" ) { # print < # # # # # # #EOF # } print < EOF } sub print_html_table_line() { my ($str_cell_0,$str_cell_1,$str_cell_2,$str_cell_3,$str_cell_4) = @_; $str_cell_0 = " " unless $str_cell_0 ; $str_cell_1 = " " unless $str_cell_1 ; $str_cell_2 = " " unless $str_cell_2 ; $str_cell_3 = " " unless $str_cell_3 ; $str_cell_4 = " " unless $str_cell_4 ; # if ((($count) % 2) && ($color_row_2 =~ /./)) { print < EOF } else { print < EOF } $count++ ; print <$str_cell_0 EOF } # 删除字符串前后空格的函数 sub trim () { my $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; } sub getopts() { my $help; exit 1 if ! GetOptions( "cvslog=s"=>\$m_cvs_log_file, "title=s"=>\$m_report_title, "help"=>\$help, "version"=>\$help, ); usage() if $help; } sub usage() { # ©right(); my $usage_str = qq~ 从CVS LOG文件中统计出代码修改情况. 使用: cvs_stat.exe [ 选项 ] 选项: --cvslog=CVS LOG文件名 默认值:cvs_log.txt --title=TITLE 默认值空 --help --version 作者: honghunter (honghunter\@gmail.com honghunter\@126.com) ~; print $usage_str; exit 0; } ##&WriteToCSVFile($temp_commit_day, $temp_author, $temp_commit_lines, $temp_commit_lines_add, $temp_commit_lines_dec, $temp_commit_lines_new ); sub WriteToCSVFile { my ($temp_commit_day, $temp_author, $temp_commit_lines_all, $temp_commit_lines_add, $temp_commit_lines_dec, $temp_commit_new_files) = @_; my $CSV_file = "B:/stat_cvs_pl_mod.csv"; my @CSV_BODY; if (-e $CSV_file) { open(TMPDATA, "<$CSV_file") || die "$!"; while () { push @CSV_BODY, $_; } close TMPDATA; } else { # push @CSV_BODY, "日期,用户,LOC总,LOC增,LOC删,新增文件数\n"; } push @CSV_BODY, "$m_report_title,$temp_commit_day,$temp_author,$temp_commit_lines_all,$temp_commit_lines_add,$temp_commit_lines_dec,$temp_commit_new_files\n"; open FILE, ">".$CSV_file; print FILE @CSV_BODY; close FILE; } ##&quick_check_day('06-12-14','07-01-01',"", \%hash_time) ; sub quick_check_day { my $op_day = shift; my $availability_day_begin = shift; my $availability_day_end = shift; my $ref_hash = shift ; my %hash_time_result = %$ref_hash ; use Time::localtime; my $tm = localtime($TIME); my $result = 0 ; my $op_time = 0; my $adb_time = 0; my $ade_time = 0; if ( $hash_time_result{'init'} ) { # stat time Cyc inited $adb_time = $hash_time_result{'begin'} ; $ade_time = $hash_time_result{'end'} ; } else { if ( "$availability_day_end" eq "" ) { my $year = substr("$availability_day_begin",0,2); $year = $year + 1; if ( length("$year") < 2 ) { $year = "0$year"; } $availability_day_end = "$year" . "-01-01" ; # print "availability_day_end=$availability_day_end\n"; } $adb_time = &ConvertYMDStrToEpochSeconds("$availability_day_begin") ; $ade_time = &ConvertYMDStrToEpochSeconds("$availability_day_end") ; $hash_time_result{'begin'} = $adb_time ; $hash_time_result{'end'} = $ade_time ; } $op_time = &ConvertYMDStrToEpochSeconds("$op_day") ; if ( $op_time >= $adb_time ) { if ( $op_time <= $ade_time ) { # print "OK \n"; $result = 0 ; } else { # print "late. \n"; $result = 1 ; } } else { # print "early. \n"; $result = -1 ; } return $result ; } ##&check_day('06-12-14','07-01-01',"") ; sub check_day { my $op_day = shift; my $availability_day_begin = shift; my $availability_day_end = shift; use Time::localtime; my $tm = localtime($TIME); my $result = 0 ; if ( "$availability_day_end" eq "" ) { my $year = substr("$availability_day_begin",0,2); $year = $year + 1; if ( length("$year") < 2 ) { $year = "0$year"; } $availability_day_end = "$year" . "-01-01" ; # print "availability_day_end=$availability_day_end\n"; } my $op_time = &ConvertYMDStrToEpochSeconds("$op_day") ; my $adb_time = &ConvertYMDStrToEpochSeconds("$availability_day_begin") ; my $ade_time = &ConvertYMDStrToEpochSeconds("$availability_day_end") ; if ( $op_time >= $adb_time ) { if ( $op_time <= $ade_time ) { # print "OK \n"; $result = 0 ; } else { # print "late. \n"; $result = 1 ; } } else { # print "early. \n"; $result = -1 ; } return $result ; } sub ConvertYMDStrToEpochSeconds { my $str_ymd = shift ; my $year = ""; my $mon = ""; my $day = ""; if ( length("$str_ymd") == length("yy-mm-dd") ) { $year = "20". substr("$str_ymd",0,2); $mon = substr("$str_ymd",3,2) - 1 ; $day = substr("$str_ymd",6,2); # print "year=$year, mon=$mon, day=$day\n"; } elsif ( length("$str_ymd") == length("yyyy-mm-dd") ) { $year = substr("$str_ymd",0,4); $mon = substr("$str_ymd",5,2) - 1 ; $day = substr("$str_ymd",8,2); # print "year=$year, mon=$mon, day=$day\n"; } else { # error return -1 ; } return &ConvertYMDtoEpochSeconds($year, $mon, $day) ; } sub ConvertYMDtoEpochSeconds { my $year = shift; my $mon = shift; my $day = shift; use Time::Local; my $es_time = timelocal(0, 0, 0, $day, $mon, $year); # print "ES TIME=$es_time\n"; return $es_time ; } #EOF
$str_cell_0 $str_cell_1 $str_cell_2 $str_cell_3 $str_cell_4
           
         
$str_cell_0 $str_cell_1 $str_cell_2 $str_cell_3 $str_cell_4
$str_cell_1 $str_cell_2 $str_cell_3 $str_cell_4