📄 cvs2cl.pl
字号:
#!/bin/shexec perl -w -x $0 ${1+"$@"} # -*- mode: perl; perl-indent-level: 2; -*-#!perl -w################################################################# ###### cvs2cl.pl: produce ChangeLog(s) from `cvs log` output. ###### ################################################################### $Revision: 1.2 $## $Date: 2004/07/17 23:37:51 $## $Author: knilch $##use strict;use File::Basename qw( fileparse );use Getopt::Long qw( GetOptions );use Text::Wrap qw( );use User::pwent qw( getpwnam );# The Plan:## Read in the logs for multiple files, spit out a nice ChangeLog that# mirrors the information entered during `cvs commit'.## The problem presents some challenges. In an ideal world, we could# detect files with the same author, log message, and checkin time --# each <filelist, author, time, logmessage> would be a changelog entry.# We'd sort them; and spit them out. Unfortunately, CVS is *not atomic*# so checkins can span a range of times. Also, the directory structure# could be hierarchical.## Another question is whether we really want to have the ChangeLog# exactly reflect commits. An author could issue two related commits,# with different log entries, reflecting a single logical change to the# source. GNU style ChangeLogs group these under a single author/date.# We try to do the same.## So, we parse the output of `cvs log', storing log messages in a# multilevel hash that stores the mapping:# directory => author => time => message => filelist# As we go, we notice "nearby" commit times and store them together# (i.e., under the same timestamp), so they appear in the same log# entry.## When we've read all the logs, we twist this mapping into# a time => author => message => filelist mapping for each directory.## If we're not using the `--distributed' flag, the directory is always# considered to be `./', even as descend into subdirectories.# Call Tree# name number of lines (10.xii.03)# parse_options 192# derive_changelog 13# +-maybe_grab_accumulation_date 38# +-read_changelog 277# +-maybe_read_user_map_file 94# +-run_ext 9# +-read_file_path 29# +-read_symbolic_name 43# +-read_revision 49# +-read_date_author_and_state 25# +-parse_date_author_and_state 20# +-read_branches 36# +-output_changelog 424# +-pretty_file_list 290# +-common_path_prefix 35# +-preprocess_msg_text 30# +-min 1# +-mywrap 16# +-last_line_len 5# +-wrap_log_entry 177## Utilities## xml_escape 6# slurp_file 11# debug 5# version 2# usage 142# -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*-## Note about a bug-slash-opportunity:# -----------------------------------## There's a bug in Text::Wrap, which affects cvs2cl. This script# reveals it:## #!/usr/bin/perl -w## use Text::Wrap;## my $test_text =# "This script demonstrates a bug in Text::Wrap. The very long line# following this paragraph will be relocated relative to the surrounding# text:## ====================================================================## See? When the bug happens, we'll get the line of equal signs below# this paragraph, even though it should be above.";### # Print out the test text with no wrapping:# print "$test_text";# print "\n";# print "\n";## # Now print it out wrapped, and see the bug:# print wrap ("\t", " ", "$test_text");# print "\n";# print "\n";## If the line of equal signs were one shorter, then the bug doesn't# happen. Interesting.## Anyway, rather than fix this in Text::Wrap, we might as well write a# new wrap() which has the following much-needed features:## * initial indentation, like current Text::Wrap()# * subsequent line indentation, like current Text::Wrap()# * user chooses among: force-break long words, leave them alone, or die()?# * preserve existing indentation: chopped chunks from an indented line# are indented by same (like this line, not counting the asterisk!)# * optional list of things to preserve on line starts, default ">"## Note that the last two are essentially the same concept, so unify in# implementation and give a good interface to controlling them.## And how about:## Optionally, when encounter a line pre-indented by same as previous# line, then strip the newline and refill, but indent by the same.# Yeah...# Globals --------------------------------------------------------------------# In case we have to print it out:my $VERSION = '$Revision: 1.2 $';$VERSION =~ s/\S+\s+(\S+)\s+\S+/$1/;## Vars set by options:# Print debugging messages?my $Debug = 0;# Just show version and exit?my $Print_Version = 0;# Just print usage message and exit?my $Print_Usage = 0;# What file should we generate (defaults to "ChangeLog")?my $Log_File_Name = "ChangeLog";# Grab most recent entry date from existing ChangeLog file, just add# to that ChangeLog.my $Cumulative = 0;# `cvs log -d`, this will repeat the last entry in the old log. This is OK,# as it guarantees at least one entry in the update changelog, which means# that there will always be a date to extract for the next update. The repeat# entry can be removed in postprocessing, if necessary.# MJP 2003-08-02# I don't think this actually does anything usefulmy $Update = 0;# Expand usernames to email addresses based on a map file?my $User_Map_File = '';my $User_Passwd_File;my $Mail_Domain;# Output log in chronological order? [default is reverse chronological order]my $Chronological_Order = 0;# Grab user details via gecosmy $Gecos = 0;# User domain for gecos email addressesmy $Domain;# Output to a file or to stdout?my $Output_To_Stdout = 0;# Eliminate empty log messages?my $Prune_Empty_Msgs = 0;# Tags of which not to outputmy %ignore_tags;# Show only revisions with Tagsmy %show_tags;# Don't call Text::Wrap on the body of the messagemy $No_Wrap = 0;# Indentation of log messagesmy $Indent = "\t";# Don't do any pretty print processingmy $Summary = 0;# Separates header from log message. Code assumes it is either " " or# "\n\n", so if there's ever an option to set it to something else,# make sure to go through all conditionals that use this var.my $After_Header = " ";# XML Encodingmy $XML_Encoding = '';# Format more for programs than for humans.my $XML_Output = 0;my $No_XML_Namespace = 0;my $No_XML_ISO_Date = 0;# Do some special tweaks for log data that was written in FSF# ChangeLog style.my $FSF_Style = 0;# Show times in UTC instead of local timemy $UTC_Times = 0;# Show times in output?my $Show_Times = 1;# Show day of week in output?my $Show_Day_Of_Week = 0;# Show revision numbers in output?my $Show_Revisions = 0;# Show dead files in output?my $Show_Dead = 0;# Hide dead trunk files which were created as a result of additions on a# branch?my $Hide_Branch_Additions = 1;# Show tags (symbolic names) in output?my $Show_Tags = 0;# Show tags separately in output?my $Show_Tag_Dates = 0;# Show branches by symbolic name in output?my $Show_Branches = 0;# Show only revisions on these branches or their ancestors.my @Follow_Branches;# Show only revisions on these branches or their ancestors; ignore descendent# branches.my @Follow_Only;# Don't bother with files matching this regexp.my @Ignore_Files;# How exactly we match entries. We definitely want "o",# and user might add "i" by using --case-insensitive option.my $Case_Insensitive = 0;# Maybe only show log messages matching a certain regular expression.my $Regexp_Gate = '';# Pass this global option string along to cvs, to the left of `log':my $Global_Opts = '';# Pass this option string along to the cvs log subcommand:my $Command_Opts = '';# Read log output from stdin instead of invoking cvs log?my $Input_From_Stdin = 0;# Don't show filenames in output.my $Hide_Filenames = 0;# Don't shorten directory names from filenames.my $Common_Dir = 1;# Max checkin duration. CVS checkin is not atomic, so we may have checkin# times that span a range of time. We assume that checkins will last no# longer than $Max_Checkin_Duration seconds, and that similarly, no# checkins will happen from the same users with the same message less# than $Max_Checkin_Duration seconds apart.my $Max_Checkin_Duration = 180;# What to put at the front of [each] ChangeLog.my $ChangeLog_Header = '';# Whether to enable 'delta' mode, and for what start/end tags.my $Delta_Mode = 0;my $Delta_From = '';my $Delta_To = '';my $TestCode;# Whether to parse filenames from the RCS filename, and if so what# prefix to strip.my $RCS_Root;# Whether to output information on the # of lines added and removed# by each file modification.my $Show_Lines_Modified = 0;## end vars set by options.# latest observed times for the start/end tags in delta modemy $Delta_StartTime = 0;my $Delta_EndTime = 0;my $No_Ancestors = 0;my $No_Extra_Indent = 0;my $GroupWithinDate = 0;# ----------------------------------------------------------------------------package CVS::Utils::ChangeLog::EntrySet;sub new { my $class = shift; my %self; bless \%self, $class;}# -------------------------------------sub output_changelog { my $output_type = $XML_Output ? 'XML' : 'Text'; my $output_class = "CVS::Utils::ChangeLog::EntrySet::Output::${output_type}"; my $output = $output_class->new(follow_branches => \@Follow_Branches, follow_only => \@Follow_Only, ignore_tags => \%ignore_tags, show_tags => \%show_tags, ); $output->output_changelog(@_);}# -------------------------------------sub add_fileentry { my ($self, $file_full_path, $time, $revision, $state, $lines, $branch_names, $branch_roots, $branch_numbers, $symbolic_names, $author, $msg_txt) = @_; my $qunk = CVS::Utils::ChangeLog::FileEntry->new($file_full_path, $time, $revision, $state, $lines, $branch_names, $branch_roots, $branch_numbers, $symbolic_names); # We might be including revision numbers and/or tags and/or # branch names in the output. Most of the code from here to # loop-end deals with organizing these in qunk. unless ( $Hide_Branch_Additions and $msg_txt =~ /file .+ was initially added on branch \S+./ ) { # Add this file to the list # (We use many spoonfuls of autovivication magic. Hashes and arrays # will spring into existence if they aren't there already.) &main::debug ("(pushing log msg for ". $qunk->dir_key . $qunk->filename . ")\n"); # Store with the files in this commit. Later we'll loop through # again, making sure that revisions with the same log message # and nearby commit times are grouped together as one commit. $self->{$qunk->dir_key}{$author}{$time}{$msg_txt} = CVS::Utils::ChangeLog::Message->new($msg_txt) unless exists $self->{$qunk->dir_key}{$author}{$time}{$msg_txt}; $self->{$qunk->dir_key}{$author}{$time}{$msg_txt}->add_fileentry($qunk); }}# ----------------------------------------------------------------------------package CVS::Utils::ChangeLog::EntrySet::Output::Text;use base qw( CVS::Utils::ChangeLog::EntrySet::Output );use File::Basename qw( fileparse );sub new { my $class = shift; my $self = $class->SUPER::new(@_);}# -------------------------------------sub wday { my $self = shift; my $class = ref $self; my ($wday) = @_; return $Show_Day_Of_Week ? ' ' . $class->weekday_en($wday) : '';}# -------------------------------------sub header_line { my $self = shift; my ($time, $author, $lastdate) = @_; my $header_line = ''; my (undef,$min,$hour,$mday,$mon,$year,$wday) = $UTC_Times ? gmtime($time) : localtime($time); my $date = $self->fdatetime($time); if ($Show_Times) { $header_line = sprintf "%s %s\n\n", $date, $author; } else { if ( ! defined $lastdate or $date ne $lastdate or ! $GroupWithinDate ) { if ( $GroupWithinDate ) { $header_line = "$date\n\n"; } else { $header_line = "$date $author\n\n"; } } else { $header_line = ''; } }}# -------------------------------------sub preprocess_msg_text { my $self = shift; my ($text) = @_; $text = $self->SUPER::preprocess_msg_text($text);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -