3 #=======================================================================
5 # Edit cvs log messages based on output from “cvs log”
7 # Character set used in this file: UTF-8
8 # Made by Øyvind A. Holm <sunny@sunbase.org>
9 # License: GNU General Public License. See end of file for legal stuff.
10 #=======================================================================
16 our ($opt_d, $opt_h, $opt_i, $opt_s, $opt_v) =
18 getopts
('d:hisv') || die("Option error. Use -h for help.\n");
22 # When changing the version number, also update the POD.
26 our $id_date = $rcs_id;
27 $id_date =~ s/^.*?\d+ (\d\d\d\d-.*?\d\d:\d\d:\d\d\S+).*/$1/;
33 my $Simulate = $opt_s;
35 ($has_diff = 1) if (`diff --version` =~ /diff/);
36 my ($curr_rev, $curr_rcs_file, $curr_work_file, $total_ignored) =
42 my ($header_done, $subheader_done, $tmp_count) =
45 my %missing_file = ();
46 my ($start_utc, $total_skipped, $total_changed, $total_files) =
51 defined($ENV{CVSE_ROOT
}) && ($eroot_str = " -d $ENV{CVSE_ROOT}");
52 length($opt_d) && ($eroot_str = " -d $opt_d");
56 if ($Line =~ /^-{28}$/) {
59 if (length($Rev) && scalar(@Curr)) {
60 $Entry{$Rev} = join("", @Curr);
64 if ($Line =~ /^revision ([\d\.]+)/) {
67 die("Line $.: Expected \"revision \", " .
68 "got \"$Line\".\". Aborting.");
71 unless ($Line =~ /^date: \S+\s+\S+ .*/) {
72 warn("Expected \"date: \", got \"$Line\".\".");
75 unless ($Line =~ /^branches: .*;$/) {
78 $Rev = "$curr_work_file,v.$curr_rev";
80 } elsif ($Line =~ /^={77}$/) {
81 # List finished for this file, change the modified messages {{{
84 if (length($Rev) && scalar(@Curr)) {
85 $Entry{$Rev} = join("", @Curr);
88 while (my ($l_name, $l_val) = each %Entry) {
89 push(@all_revs, $l_name);
92 # Scan through all revisions {{{
94 my ($a_file, $a_rev) =
96 if ($Curr =~ /^(.+),v\.([\d\.]+?)$/) {
99 if (length($a_file) && length($a_rev)) {
100 change_message
($a_file, $a_rev, $Entry{$Curr});
103 warn("Wrong revision format \"$Curr\", " .
104 "skipping revision\n");
113 } elsif (!$header_done && $Line =~ /^RCS file: (.*)/) {
115 } elsif (!$header_done && $Line =~ /^Working file: (.*)/) {
116 $curr_work_file = $1;
118 # Regular log message {{{
126 my $Seconds = time-$start_utc;
127 printf("\n%u file%s processed%s. %u revision%s changed, " .
128 "%u revision%s skipped. %u second%s used.\n",
129 $total_files, $total_files == 1 ?
"" : "s",
130 $opt_i ?
(", $total_ignored ignored") : "",
131 $total_changed, $total_changed == 1 ?
"" : "s",
132 $total_skipped, $total_skipped == 1 ?
"" : "s",
133 $Seconds, $Seconds == 1 ?
"" : "s");
138 # Changes a log message for a specific revision of a file if it has
142 my ($File, $Rev, $Txt) = @_;
143 my $esc_file = escape_filename
($File);
145 if ($opt_i && !-e
$File) {
146 unless (defined($missing_file{$File})) {
147 print("Ignoring non-existing file $esc_file\n");
148 $missing_file{$File} = 1;
154 my $tmp_file = "cvse.$$.$tmp_count.tmp"; $tmp_count++;
155 my $compare_text = get_log_message
($esc_file, $Rev);
156 if ($Txt ne $compare_text) {
157 print("\nChanging message for $esc_file rev. $Rev ...\n");
159 if (!defined($missing_file{$File}) && !(-e
$File)) {
160 # File does not exist in this revision, change revision to
161 # make it appear and make it possible for CVS to update the
165 print("$esc_file not found, running cvs update with random " .
166 "revisions to try to make it appear...\n");
167 for my $Curr (@all_revs) {
168 if ($Curr =~ /^(.+),v\.([\d\.]+?)$/) {
170 my $ex_str = "cvs$eroot_str upd -r $t_rev $esc_file";
171 print("Executing \"$ex_str\"\n");
174 print("File exists with (old) revision $t_rev, " .
175 "CVS is now able to change the log message.\n");
180 if (!-e
$File && !defined($missing_file{$File})) {
181 warn("$esc_file: File does still not exist, messages for " .
182 "this file will not be changed\n");
183 $missing_file{$File} = 1;
187 my @Arr = split(/\n/, $Txt);
188 if (open(TxtFP
, ">$tmp_file")) {
191 if (/^date: .*/ || /^branches: .*/) {
194 print(TxtFP
"$Line\n");
197 close(TxtFP
) || die("$tmp_file: Error closing file: $!");
198 my $exec_str = "cvs$eroot_str admin " .
199 "-m$Rev:\"`cat $tmp_file`\" $esc_file";
201 $Deb = get_log_message
($esc_file, $Rev);
203 if (open(DiffFP
, ">BEFORE.cvse")) {
208 print("==== BEFORE: $esc_file $Rev \x7B\x7B\x7B ====\n" .
209 "$Deb==== \x7D\x7D\x7D ====\n");
211 printf("%s \"%s\"\n", $Simulate ?
"Simulating"
212 : "Executing", $exec_str);
213 system($exec_str) unless $Simulate;
214 $Deb = get_log_message
($esc_file, $Rev);
215 unlink($tmp_file) || warn("$tmp_file: Cannot remove file: $!");
217 if (open(DiffFP
, ">AFTER.cvse")) {
223 "==== Log diff for $File,v $Rev \x7B\x7B\x7B ====\n",
224 `diff -u BEFORE.cvse AFTER.cvse`,
225 "==== \x7D\x7D\x7D ====\n"
228 for ("BEFORE.cvse", "AFTER.cvse") {
229 unlink($_) || warn("$_: Cannot remove file: $!");
232 print("==== AFTER : $esc_file $Rev \x7B\x7B\x7B ====\n" .
233 "$Deb==== \x7D\x7D\x7D ====\n") if $opt_v;
237 warn("Cannot open temporary file \"$tmp_file\", " .
238 "log messages not changed: $!");
242 print("Message for $esc_file rev. $Rev is unchanged\n") if $opt_v;
248 sub get_log_message
{
249 # Returns the cvs log message for the specified revision of a file.
250 # Used by change_message().
253 my ($File, $Rev) = @_;
256 my $getl_call = "get_log_message(\"$File\", \"$Rev\")";
258 if (open(PipeFP
, "cvs$eroot_str log -r$Rev $File |")) {
259 while (my $Line = <PipeFP
>) {
260 if ($Line =~ /^={77}$/) {
262 # No /^----------------------------$/ found
263 die("Header terminator line not found in $getl_call, " .
264 "incompatible version of CVS?");
269 push(@Arr, $Line) if ($header_done);
270 if (!$header_done && $Line =~ /^-{28}$/) {
272 # FIXME: Should we die instead?
273 warn("Found extra header separator in $getl_call, " .
278 if ($Line =~ /^revision (\S+)/) {
280 unless ($check_rev eq $Rev) {
281 die("cvs log returned wrong revision \"$check_rev\", " .
282 "expected \"$Rev\"");
285 die("$getl_call expected \"^revision \", " .
286 "got \"$Line\".\".");
289 unless ($Line =~ /^date: .*;\s+author: .*;/) {
290 die("Expected \"date: \", got \"$Line\".\n");
293 unless ($Line =~ /^branches: .+;$/) {
300 # print("======= $getl_call returns: ===========\n");
301 # print(join("", @Arr));
302 # print("============\n");
303 return(join("", @Arr));
305 warn("Header separator not found, " .
306 "$getl_call returns nothing\n");
310 die("Can't open cvs pipe: $!");
315 sub escape_filename
{
316 # Kludge for handling file names with spaces and characters that
317 # trigger shell functions
321 # $Name =~ s/\\/\\\\/g;
322 # $Name =~ s/([ \t;\|!&"'`#\$\(\)<>\*\?])/\\$1/g;
330 # Send the help message to stdout
334 cvse v$VERSION -- $id_date
336 Syntax: cvse [options] [logfile [...]]
340 -d x Use x as CVSROOT instead of the cvsroot specified in CVS/Root or
341 the CVSE_ROOT environment variable.
342 -h Print this help message.
343 -i Ignore files which doesn't exist in this revision. Avoids update
345 -s Simulate only. Normal execution except the messages are not
347 -v Verbose execution, print some extra progress messages.
356 # Plain Old Documentation (POD) {{{
362 cvse -- CVSEdit -- edit CVS log messages
372 cvse [options] [logfile [...]]
376 B<cvse> is a Perl script which changes CVS log messages for one or many
377 files based on the output from a regular S<C<cvs log>> command.
378 This makes it easy to edit lots of messages and then run the script once
379 which changes all the modified messages.
381 An easy way to do this can be:
385 =item 1. Go to the directory where your source files are, or check out a
386 new revision into an empty directory.
388 =item 2. Run C<cvs log E<gt>logfile.txt>
390 =item 3. Edit F<logfile.txt> (or whatever you call it) with your
391 favourite text editor.
393 =item 4. Run C<cvse logfile.txt>
397 All the messages you modified will now be changed by CVS using the
398 S<C<cvs admin>> command.
399 Unchanged messages will not be updated.
401 Another, faster way is to just read the output into your editor, edit it
402 and filter the file through cvse.
403 An example on how to do this in the vi(1) editor:
417 Use x as CVSROOT instead of the cvsroot specified in F<CVS/Root> or the
418 C<CVSE_ROOT> environment variable.
422 Ignore files which doesn't exist in this revision.
423 Avoids update to random revisions.
427 Print a brief help summary.
432 Normal execution except the messages are not changed.
437 Print some extra progress messages.
447 Specifies which CVSROOT to use during the message update.
448 Can be used to force direct access to the repository directories to
449 speed up things a lot if the current access method is client/server
451 As an example, if you have local access to the repository and your
454 CVSROOT=user@cvs.example.com:/my/repository
458 CVSE_ROOT=/my/repository
460 to force CVS to work directly against the directory.
461 This will improve the working speed dramatically because the client
462 doesn't have to connect to the CVS server for every operation.
464 The C<-d> option will override this variable.
470 Not really bugs, but:
472 Due to the format of S<C<cvs log>> output, messages can't contain any
473 lines matching these patterns:
477 /^date: \d\d\d\d\/\d\d\/\d\d \d\d:\d\d:\d\d;\s+author: .*/
480 If any of these patterns are found, the script will either ignore the
481 line or interpret it as a message separator.
483 CVS refuses to change the log message of a file that doesn't exist in
484 the current revision.
485 When the script notices that a certain file doesn't exist on this branch
486 or in the current revision, it tries to restore an earlier revision with
487 S<C<cvs update>> to get the file in place.
488 When the file exists, CVS is able to change the log message.
489 This results in random revisions of missing files showing up, but
490 everything can be restored to normal with the usual S<C<cvs update -A>>
492 To avoid messing up source trees with lots of tags and revisions, it's
493 recommended to check out the files in a separate directory where the
494 script can work, or use the C<-i> option which ignores non-existing
496 If no revision can be restored, a warning is generated and no messages
497 for this file will be changed.
499 This only applies to files that I<does not exist>, revisions of existing
502 Please send any bug reports or suggestions to the mail address below.
506 Made by Øyvind A. Holm S<E<lt>sunny _AT_ sunbase.orgE<gt>>.
510 The newest version of the script can be found at
511 L<http://www.sunbase.org/src/cvse/>
515 Copyright © 2003–2004 Free Software Foundation, Inc.
516 This is free software; see the file F<COPYING> for legalese stuff.
520 This program is free software; you can redistribute it and/or modify it
521 under the terms of the GNU General Public License as published by the
522 Free Software Foundation; either version 2 of the License, or (at your
523 option) any later version.
525 This program is distributed in the hope that it will be useful, but
526 WITHOUT ANY WARRANTY; without even the implied warranty of
527 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
528 See the GNU General Public License for more details.
530 You should have received a copy of the GNU General Public License along
531 with this program; if not, write to the Free Software Foundation, Inc.,
532 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
542 # vim: set fdm=marker ts=4 sw=4 sts=4 et :
543 # vim: set fo+=2w fo-=n fenc=utf8 :