📄 cvs2cl.pl
字号:
my $tag_name = $1; my $tag_rev = $2; # A branch number either has an odd number of digit sections # (and hence an even number of dots), or has ".0." as the # second-to-last digit section. Test for these conditions. my $real_branch_rev = ""; if (($tag_rev =~ /^(\d+\.\d+\.)+\d+$/) # Even number of dots... and (! ($tag_rev =~ /^(1\.)+1$/))) # ...but not "1.[1.]1" { $real_branch_rev = $tag_rev; } elsif ($tag_rev =~ /(\d+\.(\d+\.)+)0.(\d+)/) # Has ".0." { $real_branch_rev = $1 . $3; } # If we got a branch, record its number. if ($real_branch_rev) { $branch_names{$real_branch_rev} = $tag_name; if (@Follow_Branches) { if (grep ($_ eq $tag_name, @Follow_Branches)) { $branch_numbers{$tag_name} = $real_branch_rev; } } } else { # Else it's just a regular (non-branch) tag. push (@{$symbolic_names{$tag_rev}}, $tag_name); } } } # End of code for collecting tag names. # If have file name, but not revision, and see revision, then grab # it. (We collect unconditionally, even though we may or may not # ever use it.) if ((! (defined $revision)) and (/^revision (\d+\.[\d.]+)/)) { $revision = $1; if (@Follow_Branches) { foreach my $branch (@Follow_Branches) { # Special case for following trunk revisions if (($branch =~ /^trunk$/i) and ($revision =~ /^[0-9]+\.[0-9]+$/)) { goto dengo; } my $branch_number = $branch_numbers{$branch}; if ($branch_number) { # Are we on one of the follow branches or an ancestor of # same? # # If this revision is a prefix of the branch number, or # possibly is less in the minormost number, OR if this # branch number is a prefix of the revision, then yes. # Otherwise, no. # # So below, we determine if any of those conditions are # met. # Trivial case: is this revision on the branch? # (Compare this way to avoid regexps that screw up Emacs # indentation, argh.) if ((substr ($revision, 0, ((length ($branch_number)) + 1))) eq ($branch_number . ".")) { goto dengo; } # Non-trivial case: check if rev is ancestral to branch elsif ((length ($branch_number)) > (length ($revision)) and $No_Ancestors) { $revision =~ /^((?:\d+\.)+)(\d+)$/; my $r_left = $1; # still has the trailing "." my $r_end = $2; $branch_number =~ /^((?:\d+\.)+)(\d+)\.\d+$/; my $b_left = $1; # still has trailing "." my $b_mid = $2; # has no trailing "." if (($r_left eq $b_left) && ($r_end <= $b_mid)) { goto dengo; } } } } } else # (! @Follow_Branches) { next; } # Else we are following branches, but this revision isn't on the # path. So skip it. undef $revision; dengo: next; } # If we don't have a revision right now, we couldn't possibly # be looking at anything useful. if (! (defined ($revision))) { $detected_file_separator = /^$file_separator$/o; if ($detected_file_separator) { # No revisions for this file; can happen, e.g. "cvs log -d DATE" goto CLEAR; } else { next; } } # If have file name but not date and author, and see date or # author, then grab them: unless (defined $time) { if (/^date: .*/) { ($time, $author, $state, $lines) = &parse_date_author_and_state ($_); if (defined ($usermap{$author}) and $usermap{$author}) { $author = $usermap{$author}; } elsif($Domain ne "" or $Gecos == 1) { my $email = $author; if($Domain ne "") { $email = $author."@".$Domain; } my $pw = getpwnam($author); my $fullname; my $office; my $workphone; my $homephone; for (($fullname, $office, $workphone, $homephone) = split /\s*,\s*/, $pw->gecos) { s/&/ucfirst(lc($pw->name))/ge; } if($fullname ne "") { $author = $fullname . " <" . $email . ">"; } } } else { $detected_file_separator = /^$file_separator$/o; if ($detected_file_separator) { # No revisions for this file; can happen, e.g. "cvs log -d DATE" goto CLEAR; } } # If the date/time/author hasn't been found yet, we couldn't # possibly care about anything we see. So skip: next; } # A "branches: ..." line here indicates that one or more branches # are rooted at this revision. If we're showing branches, then we # want to show that fact as well, so we collect all the branches # that this is the latest ancestor of and store them in # @branch_roots. Just for reference, the format of the line we're # seeing at this point is: # # branches: 1.5.2; 1.5.4; ...; # # Okay, here goes: if (/^branches:\s+(.*);$/) { if ($Show_Branches) { my $lst = $1; $lst =~ s/(1\.)+1;|(1\.)+1$//; # ignore the trivial branch 1.1.1 if ($lst) { @branch_roots = split (/;\s+/, $lst); } else { undef @branch_roots; } next; } else { # Ugh. This really bothers me. Suppose we see a log entry # like this: # # ---------------------------- # revision 1.1 # date: 1999/10/17 03:07:38; author: jrandom; state: Exp; # branches: 1.1.2; # Intended first line of log message begins here. # ---------------------------- # # The question is, how we can tell the difference between that # log message and a *two*-line log message whose first line is # # "branches: 1.1.2;" # # See the problem? The output of "cvs log" is inherently # ambiguous. # # For now, we punt: we liberally assume that people don't # write log messages like that, and just toss a "branches:" # line if we see it but are not showing branches. I hope no # one ever loses real log data because of this. next; } } # If have file name, time, and author, then we're just grabbing # log message texts: $detected_file_separator = /^$file_separator$/o; if ($detected_file_separator && ! (defined $revision)) { # No revisions for this file; can happen, e.g. "cvs log -d DATE" goto CLEAR; } unless ($detected_file_separator || /^$logmsg_separator$/o) { $msg_txt .= $_; # Normally, just accumulate the message... next; } # ... until a msg separator is encountered: # Ensure the message contains something: if ((! $msg_txt) || ($msg_txt =~ /^\s*\.\s*$|^\s*$/) || ($msg_txt =~ /\*\*\* empty log message \*\*\*/)) { if ($Prune_Empty_Msgs) { goto CLEAR; } # else $msg_txt = "[no log message]\n"; } ### Store it all in the Grand Poobah: { my $dir_key; # key into %grand_poobah my %qunk; # complicated little jobbie, see below # Each revision of a file has a little data structure (a `qunk') # associated with it. That data structure holds not only the # file's name, but any additional information about the file # that might be needed in the output, such as the revision # number, tags, branches, etc. The reason to have these things # arranged in a data structure, instead of just appending them # textually to the file's name, is that we may want to do a # little rearranging later as we write the output. For example, # all the files on a given tag/branch will go together, followed # by the tag in parentheses (so trunk or otherwise non-tagged # files would go at the end of the file list for a given log # message). This rearrangement is a lot easier to do if we # don't have to reparse the text. # # A qunk looks like this: # # { # filename => "hello.c", # revision => "1.4.3.2", # time => a timegm() return value (moment of commit) # tags => [ "tag1", "tag2", ... ], # branch => "branchname" # There should be only one, right? # branchroots => [ "branchtag1", "branchtag2", ... ] # } if ($Distributed) { # Just the basename, don't include the path. ($qunk{'filename'}, $dir_key, undef) = fileparse ($file_full_path); } else { $dir_key = "./"; $qunk{'filename'} = $file_full_path; } # This may someday be used in a more sophisticated calculation # of what other files are involved in this commit. For now, we # don't use it much except for delta mode, because the # common-commit-detection algorithm is hypothesized to be # "good enough" as it stands. $qunk{'time'} = $time; # 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. $qunk{'revision'} = $revision; $qunk{'state'} = $state; if ( defined( $lines )) { $qunk{'lines'} = $lines; } # Grab the branch, even though we may or may not need it: $qunk{'revision'} =~ /((?:\d+\.)+)\d+/; my $branch_prefix = $1; $branch_prefix =~ s/\.$//; # strip off final dot if ($branch_names{$branch_prefix}) { $qunk{'branch'} = $branch_names{$branch_prefix}; } # Keep a record of the file's cvs state. $qunk{'cvsstate'} = $state; # If there's anything in the @branch_roots array, then this # revision is the root of at least one branch. We'll display # them as branch names instead of revision numbers, the # substitution for which is done directly in the array: if (@branch_roots) { my @roots = map { $branch_names{$_} } @branch_roots; $qunk{'branchroots'} = \@roots; } # Save tags too. if (defined ($symbolic_names{$revision})) { $qunk{'tags'} = $symbolic_names{$revision}; delete $symbolic_names{$revision}; # If we're in 'delta' mode, update the latest observed # times for the beginning and ending tags, and # when we get around to printing output, we will simply restrict # ourselves to that timeframe... if ($Delta_Mode) { if (($time > $Delta_StartTime) && (grep { $_ eq $Delta_From } @{$qunk{'tags'}})) { $Delta_StartTime = $time; } if (($time > $Delta_EndTime) && (grep { $_ eq $Delta_To } @{$qunk{'tags'}})) { $Delta_EndTime = $time; } } } # 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.) &debug ("(pushing log msg for ${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. push (@{$grand_poobah{$dir_key}{$author}{$time}{$msg_txt}}, \%qunk); } CLEAR: # Make way for the next message undef $msg_txt; undef $time; undef $revision; undef $author; undef @branch_roots; # Maybe even make way for the next file: if ($detected_file_separator) { undef $file_full_path; undef %branch_names; undef %branch_numbers; undef %symbolic_names; } } close (LOG_SOURCE); ### Process each ChangeLog while (my ($dir,$authorhash) = each %grand_poobah) { &debug ("DOING DIR: $dir\n"); # Here we twist our hash around, from being # author => time => message => filelist # in %$authorhash to # time => author => message => filelist # in %changelog. # # This is also where we merge entries. The algorithm proceeds # through the timeline of the changelog with a sliding window of # $Max_Checkin_Duration seconds; within that window, entries that # have the same log message are merged. # # (To save space, we zap %$authorhash after we've copied # everything out of it.) my %changelog; while (my ($author,$timehash) = each %$authorhash) { my $lasttime; my %stamptime; foreach my $time (sort {$main::a <=> $main::b} (keys %$timehash)) { my $msghash = $timehash->{$time}; while (my ($msg,$qunklist) = each %$msghash) { my $stamptime = $stamptime{$msg}; if ((defined $stamptime) and (($time - $stamptime) < $Max_Checkin_Duration) and (defined $changelog{$stamptime}{$author}{$msg})) { push(@{$changelog{$stamptime}{$author}{$msg}}, @$qunklist); } else { $changelog{$time}{$author}{$msg} = $qunklist; $stamptime{$msg} = $time; } } } } undef (%$authorhash); ### Now we can write out the ChangeLog! my ($logfile_here, $logfile_bak, $tmpfile); if (! $Output_To_Stdout) { $logfile_here = $dir . $Log_File_Name; $logfile_here =~ s/^\.\/\//\//; # fix any leading ".//" problem $tmpfile = "${logfile_here}.cvs2cl$$.tmp"; $logfile_bak = "${logfile_here}.bak";
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -