#!/usr/bin/env perl -w use strict 'subs'; use Getopt::Std; ($prog = $0) =~ s|.*/||; $usage = < old-name new-name [old-name new-name ...] cvs_rename I<[-vn] -x.old.new> [except ...] =head1 DESCRIPTION Renaming a file that is under the control of CVS is not so simple as mv old-name new-name CVS should have a command to do that, but it doesn't (at least as of release 1.9). Instead, it suggests no fewer than three different ways of accomplishing the task in the online documenation (topic "Moving files" in the info doc). The first and simplest is to I the old version and I the second. Unfortunately, it leaves no record of the connection between the two, and I doesn't do the right thing. The second way is simply to rename the history file ($CVSROOT/whatever/old-name,v), but that has problems as well (see the CVS info for details). The third is a variant of that that leaves old-name,v intact. That's what this program does. The first version of the call simply renames I in the current directory to I. The second version renames I to I for each file I in the current directory. Any remaining arguments are names of files that should I be renamed. It either case, it is a fatal error if a desination file exists either in the current directory or in the repository. =head1 OPTIONS =over 4 =item [-n] Don't actually do it, just indicate what would happen if you did. (Like I). =item [-v] Verbose output about what's happening. =item [-m] Additional text to prepend for the rename commit message. =back =head1 AUTHOR Marvin Solomon (solomn@cs.wisc.edu), June 1997 =cut # Argv processing die $usage unless getopts("vnx:r:m:"); # -v: verbose $opt_v = 0 unless defined $opt_v; # -n: don't do it (like make -n) $opt_n = 0 unless defined $opt_n; # -m: additional commit message to prepend $opt_m = "" unless defined $opt_m; # -r: directory containing the repository chomp($opt_r = `cat CVS/Repository`) unless $opt_r; # Find out where we are die "cannot find repository; giving up" unless $opt_r; # Actually do the renames if ($opt_x) { die "bad -x option\n" unless ($oldext,$newext) = $opt_x =~ /^\.([^.]+)\.([^.]+)$/; foreach $file (@ARGV) { $except{$file} = 1; } die "opendir" unless opendir(DIR,'.'); @files = grep { /\.$oldext$/ && !$except{$_} } readdir(DIR); closedir DIR; unless(@files) { print STDERR "nothing to be renamed\n"; exit 0; } # Make sure everything will go without a hitch before doing anything foreach $old (@files) { $new = $old; $new =~ s/$oldext$/$newext/; die "$new already exists\n" if -e "$new"; die "$opt_r/$old,v: $!\n" unless -e "$opt_r/$old,v"; die "$opt_r/$new,v already exists\n" if -e "$opt_r/$new,v"; $tags{$old} = check_status($old); } # Ok, let 'er rip $commit = ''; foreach $old (@files) { $new = $old; $new =~ s/$oldext$/$newext/; do_rename($old,$new,$tags{$old}); $commit .= " $old $new"; } $msg = "$opt_m"; $msg .= "Renamed from *.$oldext to *.$newext"; do_sys("cvs commit -f -m \"$msg\" $commit"); } else { # not opt_x die "wrong number of arguments\n$usage" unless (@ARGV > 0) && ((@ARGV % 2) == 0); # Make sure everything will go without a hitch before doing anything @args = @ARGV; while ($old = shift()) { $new = shift(); die "$new already exists\n" if -e "$new"; die "$opt_r/$old,v: $!\n" unless -e "$opt_r/$old,v"; die "$opt_r/$new,v already exists\n" if -e "$opt_r/$new,v"; $tags{$old} = check_status($old); } @ARGV = @args; # Ok, let 'er rip $commit = ''; $msg = ''; while ($old = shift()) { $new = shift(); do_rename($old,$new,$tags{$old}); if ($msg) { $msg .= " and from $old to $new"; } else { $msg = "$opt_m"; $msg .= "Renamed from $old to $new"; } $commit .= " $old $new"; } do_sys("cvs commit -f -m \"$msg\" $commit"); } exit; # Make sure file to be renamed is up-to-date with respect to the repository # Return list of tags from the cvs status command. sub check_status { local($old) = @_; local($status, $info); die "cvs status: $!\n" unless $info = `cvs status -v $old`; die "cannot parse cvs status" unless $info =~ /^\=+\nFile.*Status: (.*)/; $status = $1; die "cannot rename; file $old is $status, not Up-to-date\n" unless $status eq 'Up-to-date'; # Generate a list of tags in the old version die "cannot parse cvs status" unless $info =~ s/.*Existing Tags:\n//s; return $info; } sub do_rename { local ($old, $new, $info) = @_; # 1. Copy the file in the repository do_sys("cp $opt_r/$old,v $opt_r/$new,v"); do_sys("rm $old"); # 2. Make the version under the old name obsolete. do_sys("cvs remove $old"); # 3. Get the latest version under the new name. do_sys("cvs update $new"); # 4. Remove all tags from the new version unless ($info =~ /No Tags/) { while ($info =~ s/\s*(\S+)\s*\((\w+):.*\n//) { $tag = $1; $branch = $2; $arg = '-d'; if( $branch =~ /branch/ ) { $arg .= 'B'; } do_sys("cvs tag $arg $tag $new"); } } } # do_rename sub do_sys { local ($cmd) = @_; print STDERR "+ $cmd\n" if $opt_v; return if $opt_n; system($cmd); die "$cmd: $!" if $?; }