#!/usr/bin/perl # -*-Fundamental-*- #eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' # & eval 'exec perl -S $0 $argv:q' # if 0; ## THE PRECEEDING STUFF EXECS perl via $PATH ## -*-Fundamental-*- require 5.000; #use bytes; my $s = "\001"; # Schema stuff # my $Act_add = 0; my $Act_edit = 1; my $Act_delete = 2; my $Act_branch = 3; my $Act_integ = 4; my $Act_import = 5; # $Id: //guest/perforce_software/utils/cvs2p4/bin/dochanges#21 $ # # Richard Geiger # sub dirname { local($dir) = @_; $dir =~ s%^$%.%; $dir = "$dir/"; if ($dir =~ m%^/[^/]*//*$%) { return "/"; } if ($dir =~ m%^.*[^/]//*[^/][^/]*//*$%) { $dir =~ s%^(.*[^/])//*[^/][^/]*//*$%$1%; { return $dir; } } return "."; } use Carp; # ...or flounder. (This will fail unless 'perl' is a perl5!) $| = 1; ($Myname = $0) =~ s%^.*/%%; $Mydir = &dirname($0); $Here = `/bin/pwd`; chop $Here; if ($Mydir ne ".") { chdir "$Mydir" || die "$Myname: can't chdir \"$Mydir\": $!"; } chdir ".." || die "$Myname: can't chdir \"..\": $!"; $Mydir = `/bin/pwd`; chop $Mydir; chdir $Here || die "$Myname: can't chdir \"$Here\": $!"; #obs#use IPC::Open2; require "$Mydir/lib/util.pl"; $Usage = <] [-s ] [-c] LIT sub usage { print STDERR $Usage; exit 1; } sub help { print STDERR < set verbosity level [0] -s start numbering generated changes at -c copy (rather than link) the file archive $Myname uses the outputs of the genmetadata and genchanges stages of cvs2p4 and creates a Perforce metadatabase describing the converted changes. It also links (or, optionally, copies) the RCS archives being converted into the file archive tree under $P4ROOT. LIT exit 1; } sub path { my($prefix, $file_dir, $file_name) = @_; my($path); $path = "$prefix"; if ($file_dir) { $path .= "$file_dir/"; } $path .= "$file_name"; return $path; } sub is_binary_ext { my ($path) = @_; my ($chkpath) = ($path =~ /^$CVS_MODULE\/(.*)$/); my ($filename) = $path; $filename =~ s/^.*\///; my $fileext; $filename =~ m/\.([^\.]+)$/; $fileext = $1; $fileext =~ tr/A-Z/a-z/; if (defined($EXT{$fileext})) { return 1; } return 0; } # Determine whether the depot already has this file on this code line. # (Used to determine when we need to branch). # sub depot_has { my($file) = @_; if (defined($DEPOTMAP{$file})) { my ($have, $rev) = split(/$S/, $DEPOTMAP{$file}); return $have; } return 0; } sub depot_newrev { my($file, $action) = @_; my ($have, $rev); if ($action =~ /^(add|branch)$/) { if (defined($DEPOTMAP{$file})) { ($have, $rev) = split(/$S/, $DEPOTMAP{$file}); if ($have) { die "already have $file on add"; } $have = 1; $rev++; } else { $have = 1; $rev = 1; } } else { if (! defined($DEPOTMAP{$file})) { print "$Myname: unmapped \"$file\" on edit/delete.\n"; exit 1; } ($have, $rev) = split(/$S/, $DEPOTMAP{$file}); if (! $have) { print "$Myname: don't already have \"$file\" on edit/delete.\n"; exit 1; } if ($action eq "delete") { $have = 0; } $rev++; } $DEPOTMAP{$file} = "$have$S$rev"; return $rev; } sub genrev { my ($depotfile, $p4rev, $type, $action, $modtime, $lbrfile, $lbrrev, $lbrtype) = @_; return "$depotfile$s$p4rev$s$type$s$action$s$modtime$s//$lbrfile$s$lbrrev$s$lbrtype"; } sub mkdepot { my ($depot) = @_; print DBMETA "\@pv\@ 0 \@db.depot\@ \@$depot\@ 0 \@subdir\@ \@$depot/...\@\n"; $now = time; print DBMETA "\@pv\@ 2 \@db.domain\@ \@$depot\@ 100 \@\@ \@\@ \@cvs2p4\@ $now $now". " 0 0 \@Created by cvs2p4\n@\n"; $Made_depot{$depot} = 1; } sub genchange { my ($revs, $ch_time, $msg, $who, $integs) = @_; if ($LOGNOTE) { my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time); my($ts) = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec); $msg .= "\n[imported from CVS by cvs2p4 at $ts]\n"; } my $smsg = $msg; $smsg = substr($smsg, 0, 31); $smsg = &atq($smsg); print DBMETA "\@pv\@ 0 \@db.change\@ $Change $Change cvs2p4 $who $ch_time 1 $smsg\n"; $msg = &atq($msg); print DBMETA "\@pv\@ 0 \@db.desc\@ $Change $msg\n"; foreach my $rev (@{$revs}) { my ($depotfile, $p4rev, $type, $action, $modtime, $lbrfile, $lbrrev, $lbrtype) = split(/$s/, $rev); my ($depot) = ($depotfile =~ /^\/\/([^\/]+)\//); if (! defined($Made_depot{$depot})) { &mkdepot($depot); } $depotfile = &atq($depotfile); $lbrfile = &atq($lbrfile); $lbrrev = &atq($lbrrev); $zdig = "00000000000000000000000000000000"; $depotfile =~ s/\/Attic(\/[^\/]+)$/$1/; my $p4depotfile = &p4_esc($depotfile); print DBMETA "\@pv\@ 3 \@db.rev\@ $p4depotfile $p4rev $type $action ". "$Change $ch_time $time $zdig $lbrfile $lbrrev $lbrtype\n"; print DBMETA "\@pv\@ 0 \@db.revcx\@ $Change $p4depotfile $p4rev $action\n"; } foreach my $integ (@{$integs}) { my($tofile, $fromfile, $fromrev) = split(/$s/, $integ); my ($to_depot, $to_branch, $to_module, $to_path) = ($tofile =~ /^\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/(.*)$/); if (defined($Depotmap{$to_module})) { $tofile = "$Depotmap{$to_module}/$to_branch/$to_module/$to_path"; } my ($from_depot, $from_branch, $from_module, $from_path) = ($fromfile =~ /^\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/(.*)$/); if (defined($Depotmap{$from_module})) { $fromfile = "$Depotmap{$from_module}/$from_branch/$from_module/$from_path"; } $tofile = &atq($tofile); $fromfile = &atq($fromfile); $tofile =~ s/\/Attic(\/[^\/]+)$/$1/; $fromfile =~ s/\/Attic(\/[^\/]+)$/$1/; my $p4tofile = &p4_esc($tofile); my $p4fromfile = &p4_esc($fromfile); print DBMETA "\@pv\@ 0 \@db.integed\@ $tofile $fromfile 0 $fromrev 0 1 2 $Change\n"; print DBMETA "\@pv\@ 0 \@db.integed\@ $fromfile $tofile 0 1 0 $fromrev 3 $Change\n"; } $Change++; } # Perforce filetype bit constants # # 0x0020 to "ktext" # 0x0000 to "text" # my $TEXTTYPE_K = 0x0020; my $TEXTTYPE = 0x0000; sub type_of { my ($rcsfile, $rev, $options) = @_; my $binary = 0; if ($options =~ /[ob]/ || &is_binary_ext($rcsfile)) { $binary = 1; } elsif ($CHECKBIN) { if (&s("$CO -q -p$rev '$file' >'$Convdir/rev.tmp'")) { print "$Myname: \"$CO -p$rev '$file' >'$Convdir/rev.tmp'\" failed.\n"; exit 1; } if (-B "$Convdir/rev.tmp") { $binary = 1; } unlink "$Convdir/rev.tmp"; } my $type = 0; # NOTE: CVS/RCS just doesn't have the notion of Perforce's "text" # vs "ktext" types; in RCS, a file is either "text" (in which # keywords get expanded in one way or another), or "binary", in # which they are left untouched (RCS's concept of "binary"). How's # a poor conversion program to cope?! For now, we treat all text # files as "ktext", with an exclusion list for files which must be # plain text. # if ($binary) { $type |= 0x0100; } else { my $chkpath = $rcsfile; $chkpath =~ s/^$CVS_ROOT\///; if (defined($NOKEYEXP_PATHNAME{$chkpath})) { $type |= $TEXTTYPE; } else { $type |= $TEXTTYPE_K; } } if ($options =~ /x/) { $type |= 0x0200; } return $type; } sub set_depot_paths { my ($file_dir, $file_name, $line, $import_branch) = @_; my ($Depotfile, $Importfile); my ($module, $remainder) = ($file_dir =~ /^([^\/]+)\/?(.*)?$/); # If we are doing $USE_IMPORT_DEPOT, and $line names the branch for # 1.1.1, use "$USE_IMPORT_DEPOT" as the depot; otherwise, use that # selected by $Depotmap{}; otherwise, the default $P4_DEPOT. # if ($USE_IMPORT_DEPOT && $line eq $import_branch) { $Depotfile = &path("$USE_IMPORT_DEPOT/$line/", $remainder, $file_name); $Importfile = &path("$IMPORT/", $file_dir, $file_name); } elsif (defined($Depotmap{$module})) { $Depotfile = &path("$Depotmap{$module}/$line/$module/", $remainder, $file_name); $Importfile = &path("$IMPORT/", $file_dir, $file_name); } else { $Depotfile = &path("$P4_DEPOT/$line/", $file_dir, $file_name); $Importfile = &path("$IMPORT/", $file_dir, $file_name); } return ($Depotfile, $Importfile); } sub rmap { my ($depotrev, $cvsrev, $revinfo) = @_; $depotrev =~ s/\/Attic(\/[^\/]+)$/$1/; $REVMAP{$depotrev} = $revinfo; if ($RREVMAP{$cvsrev}) { $RREVMAP{$cvsrev} .= "\001"; } $RREVMAP{$cvsrev} .= "$depotrev"; } my %map; # Process one "change" from CVS. # Called with the set of CVS files/revs to process in @change # sub dochange { print "========== change group $do_change_num\n"; if ($V > 0) { print "NCHGS = <$#change>\n"; foreach $c (@change) { ($filerev, $time, $who, $state, $line, $import_branch, $branches, $prevrev, $options) = split(/$S/, $c); @filerev = split(/\//, $filerev); $rev = pop(@filerev); $file = join("/", @filerev); print "DUMP $who $time $filerev $state $line $import_branch $branches\n"; } } # Do the revisions... # # print "=== deletes\n"; undef @revs; my $ch_time = 0; foreach $c (@change) { chomp $c; ($filerev, $time, $who, $state, $line, $import_branch, $branches, $prevrev, $options) = split(/$S/, $c); if ($line eq $TRUNKLINE) { $line = $MAINNAME; } @filerev = split(/\//, $filerev); $rev = pop(@filerev); $file = join("/", @filerev); ($file_dir = &dirname($file)) =~ s/^$CVS_MODULE//; $file_dir =~ s/^\///; ($file_name = $file) =~ s%^.*/%%; ($Depotfile, $Importfile) = &set_depot_paths($file_dir, $file_name, $line, $import_branch); if ($state eq $DEADSTATE) { $Exists_in_depot = &depot_has($Depotfile); if ($Exists_in_depot) { $type = &type_of($file, $rev, $options); if ($time > $ch_time) { $ch_time = $time; } my $grev = &depot_newrev($Depotfile, "delete"); &rmap("$Depotfile#$grev", $filerev, $c); push(@revs, (&genrev($Depotfile, $grev, $type, $Act_delete, $time, $Importfile, $rev, $type))); } } } if ($#revs >= 0) { &genchange(\@revs, $ch_time, $MSGS{$filerev}, $who); } # print "===adds/edits===\n"; undef @revs; $ch_time = 0; foreach $c (@change) { chomp $c; ($filerev, $time, $who, $state, $line, $import_branch, $branches, $prevrev, $options) = split(/$S/, $c); if ($line eq "$TRUNKLINE") { $line = $MAINNAME; } @filerev = split(/\//, $filerev); $rev = pop(@filerev); $file = join("/", @filerev); # The was already done by the main/import handling when the 1.1 # revision was processed (Since 1.1.1.1 is, logically, on a # sub-branch of 1.1). Or so I claim. Note that we still *do* # note the creation of new sub-branches of 1.1.1.1 implied by a # non-empty $branches. # if ($rev eq "1.1.1.1" && $line eq $import_branch) { next; } ($file_dir = &dirname($file)) =~ s/^$CVS_MODULE//; $file_dir =~ s/^\///; ($file_name = $file) =~ s%^.*/%%; ($Depotfile, $Importfile) = &set_depot_paths($file_dir, $file_name, $line, $import_branch); # Note: we assume binary files never become text files or vice versa # if ($state ne $DEADSTATE) { $Exists_in_depot = &depot_has($Depotfile); if ($Exists_in_depot) { $type = &type_of($file, $rev, $options); if ($time > $ch_time) { $ch_time = $time; } my $grev = &depot_newrev($Depotfile, "edit"); &rmap("$Depotfile#$grev", $filerev, $c); push(@revs, (&genrev($Depotfile, $grev, $type, $Act_edit, $time, $Importfile, $rev, $type))); $map{"$Depotfile$s$rev"} = $grev; } else { $type = &type_of($file, $rev, $options); if ($time > $ch_time) { $ch_time = $time; } my $grev = &depot_newrev($Depotfile, "add"); &rmap("$Depotfile#$grev", $filerev, $c); push(@revs, (&genrev($Depotfile, $grev, $type, $Act_add, $time, $Importfile, $rev, $type))); $map{"$Depotfile$s$rev"} = $grev; } } } if ($#revs >= 0) { &genchange(\@revs, $ch_time, $MSGS{$filerev}, $who); } # print "===branches===\n"; undef @revs; undef @integs; $ch_time = 0; foreach $c (@change) { chomp $c; ($filerev, $time, $who, $state, $line, $import_branch, $branches, $prevrev, $options) = split(/$S/, $c); if ($line eq "$TRUNKLINE") { $line = $MAINNAME; } if ($state ne $DEADSTATE && $branches ne "-") { @filerev = split(/\//, $filerev); $rev = pop(@filerev); $file = join("/", @filerev); ($file_dir = &dirname($file)) =~ s/^$CVS_MODULE//; $file_dir =~ s/^\///; ($file_name = $file) =~ s%^.*/%%; ($Depotfile, $Importfile) = &set_depot_paths($file_dir, $file_name, $line, $import_branch); foreach $branch (split(/:/, $branches)) { $type = &type_of($file, $rev, $options); my ($Depot_branchfile) = &set_depot_paths($file_dir, $file_name, $branch, $import_branch); if ($time > $ch_time) { $ch_time = $time; } my $grev = &depot_newrev($Depot_branchfile, "add"); # if we are adding the initial import_branch revision as 1.1, # make it 1.1.1.1 # if ($branch eq $import_branch && $grev eq "1") { my ($f, $r) = ($filerev =~ m/^(.*)\/([^\/]+)$/); if ($r !~ /^1\.1(\.1\.1)?$/) { die "import_branch $import_branch assert rev '$r' !~ /^1\.1(\.1\.1)?\$/"; } $filerev = "$f/1.1.1.1"; } &rmap("$Depot_branchfile#$grev", $filerev, $c); push(@revs, (&genrev($Depot_branchfile, $grev, $type, $Act_branch, $time, $Importfile, $rev, $type))); $map{"$Depot_branchfile$s$rev"} = $grev; my $bprev; if (! ($bprev = $map{"$Depotfile$s$rev"})) { if ($rev eq "1.1.1.1") { if (! ($bprev = $map{"$Depotfile${s}1.1"})) { die "unmapped rev <$Depotfile> <1.1[.1.1]>"; } } else { die "unmapped rev <$Depotfile> <$rev>"; } } push(@integs, "$Depot_branchfile$s$Depotfile$s$bprev"); } } } if ($#revs >= 0) { &genchange(\@revs, $ch_time, "Branching\n", $who, \@integs); } } ###### main starts here # # option switch variables get defaults here... $Metadata = "metadata"; # option switch variables get defaults here... $V = 0; $Change = 1; $Copy = 0; while ($#ARGV >= 0) { if ($ARGV[0] eq "-c") { $Copy = 1; shift; next; } elsif ($ARGV[0] eq "-v") { shift; if ($ARGV[0] < 0) { &usage; } $V = $ARGV[0]; shift; next; } elsif ($ARGV[0] eq "-s") { shift; if ($ARGV[0] < 0) { &usage; } $Change = $ARGV[0]; shift; next; } elsif ($ARGV[0] eq "-help") { &help; } elsif ($ARGV[0] =~ /^-/) { &usage; } push(@Args, $ARGV[0]); shift; } if ($#Args ne 0) { &usage; } $Convdir = $Args[0]; #chdir $Convdir || die "$Myname: can't chdir \"$Convdir\": $!"; #$Convdir = `/bin/pwd`; chop $Convdir; #chdir $Here || die "$Myname: can't chdir \"$Here\": $!"; require "$Convdir/config"; if ($PureRCS) { $MAINNAME = "1"; } my ($p4d_y, $p4d_r) = &p4d_vers($P4D); if ($p4d_y < 2002) { print "$Myname: this version requires p4d 2002.1 or later <$p4d>.\n"; exit 1; } if ($CHECKBIN) { # Path the the RCS co command # if (! defined($CO)) { $CO = "/usr/local/bin/co"; } if (! -x ($CO)) { print "$Myname: No executable \"co\" command at \"$CO\".\n"; exit 1; } } $Metadata = "$Convdir/metadata"; $Logmsgs = "$Convdir/logmsgs"; $Changes = "$Convdir/changes"; $Revmap = "$Convdir/revmap"; $Rrevmap = "$Convdir/rrevmap"; $Depotmap = "$Convdir/depotmap"; $Client = "$Convdir/p4"; $DBmeta = "$P4ROOT/dbmeta"; $Checkpoint = "checkpoint"; $LOGNOTE = 1; if (! defined($P4PORT)) { print "$Myname: no P4PORT in \"$Convdir/config\".\n"; exit 1; } ($P4HOST = $P4PORT) =~ s/:.*//; ($P4PORTNUM = $P4PORT) =~ s/^.*://; $P4ADMINUSER = "p4"; # These defaults can be overriden in the config file # if ($Change == 1) { # Remove any revmap, rrevmap files... # if (&s("/bin/rm -f $Revmap $Revmap.db $Revmap.dir $Revmap.pag")) { die "/bin/rm -f $Revmap.db ..."; } if (&s("/bin/rm -f $Rrevmap $Rrevmap.db $Rrevmap.dir $Rrevmap.pag")) { die "/bin/rm -f $Rrevmap.db ..."; } if (&s("/bin/rm -f $Depotmap $Depotmap.db $Depotmap.dir $Depotmap.pag")) { die "/bin/rm -f $Depotmap.db ..."; } if (&s("/bin/rm -rf $P4ROOT && mkdir -p $P4ROOT")) { die "/bin/rm -rf $P4ROOT && mkdir -p $P4ROOT"; } } $CVS_MODPATH = $CVS_MODULE; chdir $CVS_MODPATH || die "$Myname: can't chdir \"$CVS_MODPATH\": $!"; $CVS_MODPATH = `/bin/pwd`; chop $CVS_MODPATH; chdir $Here || die "$Myname: can't chdir \"$Here\": $!"; $CVS_MODULE_NAME = $CVS_MODULE; $CVS_MODULE_NAME =~ s@/.*/@@g; $IMPORT = "$DEPOT/$CVS_MODULE_NAME"; $ndIMPORT = $IMPORT; $ndIMPORT =~ s@^[^/]+/@@; chdir $P4ROOT || die "$Myname: can't chdir \"$P4ROOT\": $!"; $P4ROOT = `/bin/pwd`; chop $P4ROOT; if (&s("/bin/mkdir -p $P4ROOT/$DEPOT")) { die "/bin/mkdir -p $P4ROOT/$DEPOT"; } if ($Change == 1) { if ($Copy || $COPYIMPORT) { my $cmd = "/bin/cp -rp \"$CVS_MODPATH\" \"$P4ROOT/$IMPORT\""; if (&s($cmd)) { print "$Myname <$cmd> failed.\n"; exit 1; } $cmd = "/usr/bin/find \"$P4ROOT/$IMPORT\" -type f -print0 | /usr/bin/xargs -0 /bin/chmod a-w"; if (&s($cmd)) { print "$Myname <$cmd> failed.\n"; exit 1; } } else { my $cmd = "/bin/ln -s \"$CVS_MODPATH\" \"$P4ROOT/$IMPORT\""; if (&s($cmd)) { print "$Myname <$cmd> failed.\n"; exit 1; } } } chdir $Here || die "$Myname: can't chdir \"$Here\": $!"; # Open the changes file (this is the genchanges output file) # if (! open(CHGS, "<$Changes")) { print "$Myname: can't open \"$Changes\": $!\n"; exit 1; } # Open the db metadata (journal format) file we will write # if (! open(DBMETA, ">$DBmeta")) { print "$Myname: can't open \"$DBmeta\": $!\n"; exit 1; } use DB_File; $DBMCLASS="DB_File"; #$myhashinfo = new DB_File::HASHINFO; #$myhashinfo->{bsize}=4096; $myhashinfo = new DB_File::BTREEINFO; # Open the REVMAP database... # if (! tie(%REVMAP, $DBMCLASS, $Revmap, O_CREAT|O_RDWR, 0666, $myhashinfo)) { print "$Myname: can't tie \"$Revmap\": $!\n"; exit 1; } # Open the RREVMAP database... # if (! tie(%RREVMAP, $DBMCLASS, $Rrevmap, O_CREAT|O_RDWR, 0666, $myhashinfo)) { print "$Myname: can't tie \"$Rrevmap\": $!\n"; exit 1; } # Open the DEPOTMAP database... # if (! tie(%DEPOTMAP, $DBMCLASS, $Depotmap, O_CREAT|O_RDWR, 0666, $myhashinfo)) { print "$Myname: can't tie \"$Depotmap\": $!\n"; exit 1; } # Open the log messages database # if (! tie(%MSGS, $DBMCLASS, $Logmsgs, O_RDONLY, 0444, $myhashinfo)) { print "$Myname: can't tie \"$Logmsgs\": $!\n"; exit 1; } # # ($depotname) = ($P4_DEPOT =~ m/^\/\/([^\/]+)\/?/); my %Made_depot; my $import_depot = $DEPOT; $import_depot =~ s/\/.*$//; &mkdepot($import_depot); if ($USE_IMPORT_DEPOT) { my $ndUSE_IMPORT_DEPOT = $USE_IMPORT_DEPOT; $ndUSE_IMPORT_DEPOT =~ s/^\/\///; &mkdepot($ndUSE_IMPORT_DEPOT); } $gather_change_num = $do_change_num = 0; while () { if (/^# ([0-9]+)$/) { $gather_change_num = $1; if ($do_change_num) { &dochange(@change); $do_change_num = 0; } # Clear the change # undef @change; } elsif ($gather_change_num) { push(@change, $_); $do_change_num = $gather_change_num; } } if ($do_change_num) { &dochange(@change); } $Change--; print DBMETA "\@pv\@ 0 \@db.counters\@ \@change\@ $Change\n"; # We've written an upgrade-level 3 compliant database. # print DBMETA "\@pv\@ 0 \@db.counters\@ \@upgrade\@ 3\n"; close DBMETA; untie %MSGS; close CHGS; untie %REVMAP; untie %RREVMAP; untie %DEPOTMAP; $DBmeta =~ s%^.*/%%; $P4D = "$P4D -r ."; if (&s("cd $P4ROOT && $P4D -jr $DBmeta")) { print "$Myname: \"$P4D -jr $DBmeta\" failed.\n"; exit 1; } if ($p4d_y > 2002 || $p4d_r > 1) { # Then we could need a database upgrade. Let's go for it... # if (&s("cd $P4ROOT && $P4D -xu")) { print "$Myname: \"$P4D -xu\" failed.\n"; exit 1; } } if (&s("cd $P4ROOT && rm -f $Checkpoint && $P4D -jd $Checkpoint")) { print "$Myname: \"$P4D -jd $Checkpoint\" failed.\n"; exit 1; } exit 0;