dochanges #57

  • //
  • guest/
  • richard_geiger/
  • utils/
  • cvs2p4/
  • bin/
  • dochanges
  • View
  • Commits
  • Open Download .zip Download (27 KB)
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/richard_geiger/utils/cvs2p4/bin/dochanges#57 $
#
#  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 = <<LIT;
$Myname: usage: $Myname [-v <n>] [-s <change>] [-c] <conversiondir>
LIT


sub usage
{
  print STDERR $Usage;
  exit 1;
}


sub help
{
  print STDERR <<LIT;
$Usage

  -v <n>       set verbosity level [0]
  -s <change>  start numbering generated changes at <change>
  -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, $spec) = @_;

  my $depot_type;
  my $depot_extra;

  if ($spec)
    {
      $depot_type = "2";
      $depot_extra = ".p4s";
    }
  else
    {
      $depot_type = "0";
      $depot_extra = "subdir";
    }

  print DBMETA "\@pv\@ 0 \@db.depot\@ \@$depot\@ $depot_type \@$depot_extra\@ \@$depot/...\@\n";
  $now = time;
  print DBMETA "\@pv\@ 2 \@db.domain\@ \@$depot\@ 100 \@\@ \@\@ \@$CONVUSER\@ $now $now".
               " 0 0 \@Created by cvs2p4\n@\n";

  print DEPOTS "$depot\n";

  $Made_depot{$depot} = 1;
}

# This is a hash, keyed by branch name. Each element is a list.
# The first list element is the db.domain record to create a branch.
# The second list element is an anonymous hash, keyed by depot name,
# with the values being the db.view records needed to reflect the
# branch within the given depot.
#

my %Branches; 

sub mkbranch
{
  my ($from, $to, $from_depot, $to_depot, $from_user, $to_user) = @_;

  # There's no branch spec for "main"
  #
  if ($to eq "main")
    {
      $Branched{"main"} = 1;
      return;
    }

  if (! $from) { $from = "main"; }
  if (! $to) { $to = "main"; }

  my $qto;
  if ($to_user)
    { $qto = &atq("$to_user-$to"); }
  else
    { $qto = &atq("$to"); }

  my $time = time;

  if (! defined($Branches{$to}))
    {
      $Branches{$qto} = [];
      ${$Branches{$qto}}[0] = "\@pv\@ 2 \@db.domain\@ $qto 98 \@\@ \@\@ \@$CONVUSER\@ $time $time 0 0 \@Created by cvs2p4.\@\n";
      ${$Branches{$qto}}[1] = {}
    }

  my $depotrefs = ${$Branches{$qto}}[1]; # copy the reference for convenience...

  my $k = "$from_user=$from_depot=$to_user=$to_depot";

  if (! defined(${$depotrefs}{$k}))
    {
      my $qlhs;
      if ($to_user)
        { $qlhs = &atq("//$to_depot/$to_user/$to/..."); }
      else
        { $qlhs = &atq("//$to_depot/$to/..."); }

      my $qrhs;
      if ($from_user)
        { $qrhs = &atq("//$from_depot/$from_user/$from/..."); }
      else
        { $qrhs = &atq("//$from_depot/$from/..."); }

      ${$depotrefs}{"$k"} = "\@pv\@ 1 \@db.view\@ $qto 0 0 $qlhs $qrhs\n";
    }
}


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";
  
  foreach my $rev (@{$revs})
    {
      my ($depotfile, $p4rev, $type, $action, $modtime, $lbrfile, $lbrrev, $lbrtype) = split(/$s/, $rev);

      $depotfile = &atq($depotfile);
      $lbrfile   = &atq($lbrfile);
      $lbrrev    = &atq($lbrrev);
      $zdig      = "00000000000000000000000000000000";

      $depotfile =~ s/\/Attic(\/[^\/]+)$/$1/;

      if ($Pathhacks) { $depotfile = &Pathhacks($depotfile); }

      my ($depot) = ($depotfile =~ /^@\/\/([^\/]+)\//);
      if (! defined($Made_depot{$depot})) { &mkdepot($depot); }

      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";

      if ($REVRECS)
        { $msg .= "$depotfile $p4rev $type $action $modtime $lbrfile $lbrrev $lbrtype\n"; }
    }

  $msg = &atq($msg);
  print DBMETA "\@pv\@ 0 \@db.desc\@ $Change $msg\n";

  foreach my $integ (@{$integs})
    {
      my($tofile, $fromfile, $fromrev) = split(/$s/, $integ);

      my $to_user;
      my ($to_depot, $to_branch, $to_module, $to_path) =
        ($tofile =~ /^\/\/([^\/]+)\/([^\/]+)\/([^\/]+)(\/.*)?$/);

# Methinks the case below is handled when the files are first seen when the revs are
# processed; thus no path adjustments are needed here. Wonder how long this
# has been dead code?!

# dead!?
#
#      if ("//$to_depot" ne $USE_IMPORT_DEPOT && defined($Depotmap{$to_module}))
#        {
#          $tofile = $Depotmap{$to_module};
#	  if ($to_branch ne "main")
#            {
#              if ($INTOUCH)
#                { $tofile =~ s/\/main\//\/task\/$to_branch\//; } # for inTouch's convention
#              else
#                { $tofile =~ s/\/main\//\/$to_branch\//; }       # for general use
#            }
##          $tofile .= "/$to_module$to_path";
#          $tofile .= "/$to_module$to_path";
#print STDERR "TOFILE mod $to_module <$tofile>\m";
#die "to_module";
#        }

      if ($Pathhacks && $to_depot eq "user")
        {
          # The things I will not do for my users!
          ($to_depot, $to_branch, $to_user, $to_module, $to_path) =
             ($tofile =~ /^\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/]+)(\/.*)?$/);
        }

      my $from_user;
      my ($from_depot, $from_branch, $from_module, $from_path) =
        ($fromfile =~ /^\/\/([^\/]+)\/([^\/]+)\/([^\/]+)(\/.*)?$/);

      if ($Pathhacks && $from_depot eq "user")
        {
          # The things I will not do for my users!
          ($from_depot, $from_branch, $from_user, $from_module, $from_path) =
             ($fromfile =~ /^\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/]+)(\/.*)?$/);
        }

      # (We don't make branch specs in //import depots)
      #
      if ("//$to_depot" ne $USE_IMPORT_DEPOT && "//$from_depot" ne $USE_IMPORT_DEPOT)
        { &mkbranch($from_branch, $to_branch, $from_depot, $to_depot, $from_user, $to_user); }

# dead!?
#
#      if (defined($Depotmap{$from_module}))
#        {
#          $fromfile = $Depotmap{$from_module};
#	  if ($from_branch ne "main")
#            {
#              if ($INTOUCH)
#                { $fromfile =~ s/\/main\//\/task\/$from_branch\//; }	# for inTouch
#              else
#                { $fromfile =~ s/\/main\//\/$from_branch\//; }		# for general use
#            }
#	  $fromfile .= "/$from_module$from_path";
#print STDERR "FROM mod $from_module <$fromfile>\n";
#die "from_module";
#        }

      $tofile = &atq($tofile);
      $fromfile = &atq($fromfile);

      if ($Pathhacks)
        {
          $tofile = &Pathhacks($tofile);
          $fromfile = &Pathhacks($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++;

}

#
#  ***** NOTE *****: We are writing the metadata format for a 2002.1
#  p4d, which has db.rev version 3; the definitions for the FileType
#  field have changed in later versions of Perforce. Perforce will
#  upgrade this format to all subsequent releases (at the time of this
#  writing), but be aware that, if you tweak any filetype bits below,
#  you should be using the 2002.1 definitions; see for reference
#
#    http://www.perforce.com/perforce/doc.021/schema/index.html#DmtFileType
#

#  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 $lockedit = 0;
   if ($options =~ /bx/) { $lockedit = 1; } # CVSNT uses x for exclusive open

   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 ($lockedit)
     { $type |= 0x0040; }

   if ($options =~ /\*/)
     { $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}))
    {
      $mapent = $Depotmap{$module};
      if ($line ne "main")
        {
          if ($INTOUCH)
            { $mapent =~ s/\/main\//\/task\/$line\//; }	# for inTouch
          else
            { $mapent =~ s/\/main\//\/$line\//; }	# for general use
        }
      $Depotfile = &path("$mapent/", $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";
$Depotlist = "$Convdir/depots";
$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)
  {
    my $destdir = dirname("$P4ROOT/$IMPORT");
    if (&s("/bin/mkdir -p $destdir"))
      { die "/bin/mkdir -p $destdir"; }

    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; }

#  Open the depot list, needed for making label maps in dolabels
#
if (! open(DEPOTS, ">$Depotlist"))
  { print "$Myname: can't open \"$Depotlist\": $!\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 (<CHGS>)
  {
    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); }


# Now output the branch metadata we have accumulated in $Branches...
#
foreach my $brname (sort(keys(%Branches)))
  {
    print DBMETA ${$Branches{$brname}}[0];
    my $r = ${$Branches{$brname}}[1];
    foreach my $k (keys(%{$r})) { print DBMETA ${$r}{$k}; }
  }


if ($SPEC_DEPOT) { &mkdepot($SPEC_DEPOT, 1); }


$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;
close DEPOTS;

$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;
# Change User Description Committed
#57 6301 Richard Geiger These changes resulted from a recent customer conversion.
A checkpoint, more or less.
#56 5712 Richard Geiger Handle Pathhacks in srcdiff & dolabels.
Moves sub Pathhacks() into util.pl
#55 5707 Richard Geiger Ironport local hacks.
Not intended for general consumption!
#54 5706 Richard Geiger Makes branch specs now.
#53 5705 Richard Geiger Fix to integ history generation when USE_IMPORT_DEPOT and
Depotmap are used....
#52 5701 Richard Geiger add comment about using the 2002.1 schema if the filetype bits
are to be tweakened.
#51 5688 Richard Geiger consistently invoke perl via PATH
#50 5676 Richard Geiger Add $CONVUSER to set owner for label and depot specs create dby the
conversion.
#49 5672 Richard Geiger dochanges now remembers the list of known depots so dolabels
can make explicit global mappings.
#48 5668 Richard Geiger REVRECS (mainly).

cc: roger@icmanage.com, shiv@icmanage.com
#47 5646 Richard Geiger Make sure we have the leading directories if both
       $CVS_ROOT and $CVS_MODULE are defined.
#46 5623 Richard Geiger $SPEC_DEPOT feature added.
#45 5615 Richard Geiger more depot mapping fixes.
#44 5611 Richard Geiger Fix a broken Depotmap{}.
#43 5610 Richard Geiger This just tightens up some pattern matches for Perforce depot-syntax
names. Should be no function change, but it's probably safer this way.
#42 5597 Richard Geiger Add news USE_IMPORT_DEPOT switch, to allow you to
have cvs-import'ed revisions (the "vendor branch") end up
in a different depot than locally-authored revisions.

This should pretty much complete feature work in 3.0;
#41 5583 Richard Geiger Handle "..." in CVS pathnames by changing them to ",,,"s.
"Works for me!"
#40 5582 Richard Geiger Fixes a problem that prevented the NOKEYEXP_PATHNAME list
from working.
#39 5571 Richard Geiger Fix the "NOKEYEXP_PATHNAME" mechanism...
so that it works :-)
#38 5569 Richard Geiger Just a variable name change:
NOEXC_PATHNAME in config is now NOKEYEXP_PATHNAME.
#37 5555 Richard Geiger NOEXC_PATHNAME
#36 5542 Richard Geiger checkpointing progress.
#35 5541 Richard Geiger fix a bug where the equivalance of 1.1 and 1.1.1.1 is not recognized.
#34 5531 Richard Geiger A significant checkpoint commit, with new improved handling
of import vendor branches, and revisions present in main
by virtue of multiple vendor drops to a file with no local mods.

test/runtest works, with new refernece results pretty well
scrutinized.
#33 5510 Richard Geiger Add the $Depotmap{"somecvstopdir"} = "//somep4depot"; facility
#32 5509 Richard Geiger checkpoint
#31 5490 Richard Geiger $Depotmap implemented.
#30 5442 Richard Geiger A checkpoint commit on the way to a 2.6.0 release with the
new IronPort inspired improvements.
#29 5437 Richard Geiger just another checkpoint. Passed test/runtest, but
still has debugging cruft in. Not fit for release!
#28 5430 Richard Geiger This is another "checkpoint" commit.
 It significantly
rearranges how labels are done, so as to use a hueristic to
divine label<->branch identifications. Not intended for
release without further testing and tweakage!
#27 4914 Richard Geiger Adds PureRCS switch.
#26 4885 Richard Geiger Allow using a name other than "main" for the TRUNKLINE branch.
#25 4732 Richard Geiger Changes to support special characters # @ % * (for release 2.5)
#24 4276 Richard Geiger fix dochanges so that <test_conv_dir>/depotmap is run correctly.
#23 3708 Richard Geiger Changes for 2.3.6
#22 3594 Richard Geiger Changes for 2.3.5
#21 2061 Richard Geiger changes for 2.3.2:
  - can adjust db hash bucket size;
  - Add $DEPOT config variable
  - Handle labels with '#' or '@'
#20 1987 Richard Geiger Changes for 2.3.1
#19 1977 Richard Geiger Explicit p4d -r
#18 1967 Richard Geiger Add new config item "DEADSTATE".
#17 1965 Richard Geiger Fix bug where link was being made if $COPY succeedes.
#16 1964 Richard Geiger Add $TEXTTYPE and surrounding accoutrements.
#15 1781 Richard Geiger This change reintegrates cvs2p4 2.0 developement work (through
2.0b6) back into my mainline development.
#14 1637 Richard Geiger Was removing "Revmap.db" not "$Revmap.db".
#13 1407 Richard Geiger === Release 1.3.2

- Reduce the memory footprint of bin/genmetadata. Previously, it was
  holding and sorting a complete copy of the metadata file "in-core"
  (as well as a copy of all of the RCS revision tags data!). This adds
  up quick, and some users saw genmetadata gobbling memory voraciously
  (and in some cases being running out, causing thrashing and/or process
  termination by the OS).

  genmetadata now keeps the metadata in a temp file, (sorting it in
  primary-key-sized chunks), and the revision tag information in a
  db-backed hash.

- Fix the label handling so that _all_ perforce revisions based on the
  labeled cvs revision are included in the generated
  labels. Previously, one of the N "correct" Perforce revisions were
  being tagged (effectively, at random). This stems from the fact that
  lazy copying and branching are explicit in Perforce, but implicit in
  CVS. I.e., the "#1" revision in a new Perforce branch _appears_ to
  be a separate entity (identical to the revision from which it was
  branched. This means that to use the converted labels, it will be up
  to _you_ to remember what labels go with what branches: but that's
  the way it is in CVS, too.

- A minor change in revmap to have a meaningful usage message, and
  properly handle the new rrevmap format.

- dochanges correctly deletes revmap database files for either
  *.db or *.pag/*.dat style databases.
#12 1185 Richard Geiger Changes for 1.3
(Labels!)
#11 1172 Richard Geiger Complete the toleracne of change renumbering.
#10 1150 Richard Geiger Allow messages about change renumbering, so cvs2p4 can be used
to import to an existing (nonvirginal!) server. Note that I
*don't* claim this works completely now, but from my
experience at Orca a year ago, this was the only bugaboo that
bit. Note also that it's still the user's obligation to insure
that there are no pathname overlaps with files that already
exist. *That* is guarenteed
       not to work!
#9 791 Richard Geiger Update for a new release; my change-of-venue noted;
desensitized to conflicting P4CONFIG.
#8 421 Richard Geiger Fix to the fix by Thomas Quinot.
#7 416 Richard Geiger Pull in Thomas Quinot <quinot@inf.enst.fr>'s UTC bugfix, for 1.2.12.
#6 250 Richard Geiger Changes to allow coping with shell metacharacters (' ' & '$', at least)
in file names.
#5 249 Richard Geiger Changes in preparation for supporting spaces in filenames.
(In fact, this may work as of this change, but is not yet tested.)
Also, add "runtest -gengood" to allow easier generatino of new *.good
files. (It just doesn't quick on a miscompare!).
#4 240 Richard Geiger Version 1.2.5, to account for post-1999 RCS behavior.
(Courtesy of David Simon, Goldman Sachs)
#3 228 Richard Geiger Changes for 1.2.4 - recognize alternative "p4 files" message
for files not present in the depot.
#2 179 Richard Geiger CHanges for 1.2.3
#1 130 Richard Geiger CVS-to-Perforce converter.
This is release 1.2.2
(first submit to the Perforce Public Depot)