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;

#  $Id: //guest/rick_richardson/perforce/utils/cvs2p4/bin/dochanges#1 $
#
#  Richard Geiger
#
#  These re's specify the set of expected messages from various p4
#  operations which are successful. I.e., if a message is seen that
#  doe *not* match one of these, it's considered an error.
#
#     ... for "p4 submit"s:
#
$check_submits = <<MSGS;
Change [0-9]+ created with [0-9]+ open file\\(s\\).
^Change [0-9]+ submitted.\$
^Locking [0-9]+ files ...\$
^Submitting change [0-9]+.\$
^(add|branch|edit|delete) //.+#[0-9]+\$
^//.+ - refreshing\$
MSGS
@check_submits = split(/\n/, $check_submits);

#
#     ... for "p4 change -f"s:
#
$check_changefs = <<MSGS;
Change [0-9]+ updated.\$
MSGS
@check_changefs = split(/\n/, $check_changefs);

#print "\@check_submits:\n";
#foreach $c (@check_submits) { print "$c\n"; }
#obs##print "\n\@check_changes:\n";
#obs##foreach $c (@check_changes) { print "$c\n"; }
#print "\n\@check_changefs:\n";
#foreach $c (@check_changefs) { print "$c\n"; }
#exit 1;

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

$Convclient = "cvs2p4";

$Usage = <<LIT;
$Myname: usage: $Myname [-v <n>] [-s <start_change_group>] <conversiondir>
LIT


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


sub help
{
  print STDERR <<LIT;
$Usage
$Myname <to be written>
LIT
  exit 1;
}


#  Guarantee that the name directory (may be multiple levels deep)
#  exists; create intermediate directories as needed (like mkdir -p)
#
sub insuredir
{
  my($d) = @_;
  if (! -d $d)
    {
      my($p) = &dirname($d);
      &insuredir($p);
      mkdir $d, 0755;
    }
}


sub path
{
  my($prefix, $file_dir, $file_name) = @_;
  my($path);
  $path = "$prefix";
  if ($file_dir) { $path .= "$file_dir/"; }
  $path .= "$file_name";
  return $path;
}


#  Check a message; if it matches any of the expected ones on @res,
#  it's OK; otherwise, declare a problem and rithlessly exit.  We
#  demand perfection!
#
sub checkmsg
{
  my($msg, $from, @res) = @_;
  chop $msg;
  foreach $re (@res)
    { if ($msg =~ /$re/) { return; } }
  print "$Myname: checkmsg: *** $from *** $msg\n";
  exit 1;
}  

# A temp file used to hold p4 command output
#
$tmpout = "/tmp/p4_submit_out.$$";

#  A convenient switch for disabling actual exection of commands.
#
$Do = 1;

#  Submit the current default change.
#  $msg is the log message to use.
#  $who is the original author of the RCS revision(s)
#  $time is the original time of the RCS revsision(s)
#  $files is the file list for the change.
#  @change is the change set; required for building revmap
#    (Used to construct the map of RCS revisions -> p4 changes.
#
sub p4submit
{
  my($msg, $who, $time, $files, @change) = @_;
  my $changenum;

  #  This bit is to allow us to pass multiple arrays.
  #  Hipper folks would use refs to arrays, I know.
  #  I must be as old as Larry Wall's Mom.
  #
  my (@files) = split(/\001/, $files);

  #  Start the submit...
  #
  $p4submit = "$P4 submit -i >$tmpout 2>&1";
  if ($V > 0) { print "$Myname: ...| $p4submit\n"; }
  if (! open(SUBMITW, "| $p4submit"))
    {
      print "$Myname: open \"| $p4submit\" failed: $!.\n";
      exit 1;
    }
 
  $P1 = <<P1;
Change:\tnew

Client:\t$Convclient

User:\t$Username

Status:\tnew

Description:
P1
  if ($V > 1) { my $vl; foreach $vl (split(/\n/, $P1)) { print "->SUBMITW: $vl\n"; } }
  print SUBMITW $P1;

  #  Insert the log message...
  #
  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 $Convclient at $ts]\n";
    }

  @msg = split(/\n/, $msg);
  foreach $line (@msg)
    {
      if ($V > 1) { print "->SUBMITW:\t$line\n"; }
      print SUBMITW "\t$line\n";
    }

  $P2 = <<P2;

Files:
P2
  if ($V > 1) { my $vl; foreach $vl (split(/\n/, $P2)) { print "->SUBMITW: $vl\n"; } }
  print SUBMITW $P2;

  #  Insert the files list...
  #
  foreach $file (@files)
    {
      if ($V > 1) { print "->SUBMITW:\t$file\n"; }
      print SUBMITW "$file\n";
    }
     
  close SUBMITW;

  # OK, now we inspect the output from "p4 submit".  (Hey, we used to
  # use open2, but it kept leaving zombies around, and I got tired of
  # trying to understand why...)
  #
  if (! open(SUBMITR, "<$tmpout"))
    {
      print "$Myname: open \"<$tmpout\" failed: $!.\n";
      exit 1;
    }

  #  Build %chrevs - the whole first field of the change line, indexed
  #  by the file path part.
  #
  foreach $chrev (@change)
    {
      ($revinfo = $chrev) =~ s/ .*//; chop $revinfo;
      $revinfo =~ m%^$CVS_MODULE/(.*)/([0-9\.]+)%;
      $chrev{$1} = $revinfo;
    }

  while (<SUBMITR>)
    {
      if ($V > 0) { print "SUBMITR->:$_"; }
      &checkmsg($_, "p4 submit", @check_submits);

      #  Now update the rev map for each Perforce rev we see...
      #
      if (/^(add|branch|delete|edit) $P4_DEPOT\/[^\/]+\/(.*)#([0-9]+)$/)
        {
	  $chpath = $2;
          $p4rev = $_; chop $p4rev; $p4rev =~ s/^.* //;
          $cvsrev = $chrev{$chpath};
	  $REVMAP{$p4rev} = $cvsrev;
        }          

      if ($_ =~ /^Change ([0-9]+) submitted\.$/) { $changenum = $1; }
    } 
  close SUBMITR;

  #  Now we spoof the change to appear as done by $who at $time:
  #
  if ($changenum !~ /[0-9]+/)
    { print "$Myname: assert: didn't see change number <$changenum>.\n"; exit 1; }

  #  Start "p4 change -i -f"
  #
  $p4changef = "$P4 change -i -f >$tmpout 2>&1";
  if ($V > 0) { print "$Myname: ...| $p4changef\n"; }
  if (! open(CHANGEW, "| $p4changef"))
    {
      print "$Myname: open \"| $p4submit\" failed: $!.\n";
      exit 1;
    }

my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time);
$Date = sprintf("%4d/%02d/%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);

$C1 = <<C1;
Change:\t$changenum

Date:\t$Date

Client:\t$Convclient

User:\t$who

Status:\tsubmitted

Description:
C1
  if ($V > 1) { my $vl; foreach $vl (split(/\n/, $C1)) { print "->CHANGEW: $vl\n"; } }
  print CHANGEW $C1;

  #  Insert the log message. Again.
  #
  foreach $line (@msg)
    {
      if ($V > 1) { print "->CHANGEW:\t$line\n"; }
      print CHANGEW "\t$line\n";
    }
  close CHANGEW;

  #  Now inspect the p4 output for errors...
  #
  if (! open(CHANGER, "<$tmpout"))
    {
      print "$Myname: open \"<$tmpout\" failed: $!.\n";
      exit 1;
    }

  while (<CHANGER>)
    {
      if ($V > 0) { print "SUBMITR->:$_"; }
      &checkmsg($_, "p4 change -f", @check_changefs);
    } 
  close CHANGER;
  unlink $tmpout;
}


$ext = <<EXTS;
a
bin
bmp
class
coff
com
dll
doc
dvi
dwarf
exe
fm
gif
gz
ico
jar
jpg
lib
o
obj
pdf
ps
tar
xbm
xls
zip
z
EXTS

@ext = split(/\n/, $ext); foreach $ext (@ext) { $ext{$ext} = 1; }

sub is_binary_file
{
  my ($path) = @_;
  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;
}

#if (&is_binary_file($ARGV[0]))
#  { print "BINARY\n"; } else { print "TEXT\n"; }
#exit 1;

#  Determine whether the depot already has this file on this code line.
#  (Used to determine when we need to branch).
#
sub depot_has
{
  my($clientfile) = @_;
  my($line);

  $p4files = "$P4 files '$clientfile' 2>&1";
  print "$Myname: $p4files |...\n";
  if (! open(FILES, "$p4files |"))
    {
      print "$Myname: open \"$p4files |\" failed: $!.\n";
      exit 1;
    }
  $line = <FILES>;
#print $line; while(<FILES>) { print $_; }
  close FILES;
  if ($line =~ /- no such file\(s\)\./
        || $line =~ / - file\(s\) not in client view./
        || $line =~ / - delete change /)
    { return 0; }
  else
    { return 1; }
}

  
#  Process one "change" from RCS.
#  Called with the set of RCS files/revs to process in @change
#
sub dochange
{
  my @map;

  print "\n========== change group $do_change_num\n";

  if ($V > 0)
    {
      foreach $c (@change)
        {
          ($filerev, $time, $who, $state, $line, $branches, $prevrev, $options) = split(/$S/, $c);
          @filerev = split(/\//, $filerev);
          $rev = pop(@filerev);
          $file = join("/", @filerev);
         print "$who $time $filerev $state $line $branches\n";
       }
    }

  #  Get the 
  #

  # Do the revisions...
  #
  print "=== deletes\n";
  $nc = 0; undef $files;
  foreach $c (@change)
    {
      ($filerev, $time, $who, $state, $line, $branches, $prevrev, $options) = split(/$S/, $c);
      if ($line eq "$TRUNKLINE") { $line = "main"; }
      @filerev = split(/\//, $filerev);
      $rev = pop(@filerev);
      $file = join("/", @filerev);

      ($file_dir = &dirname($file)) =~ s/^$CVS_MODULE//;
      $file_dir =~ s/^\///;
      ($file_name = $file) =~ s%^.*/%%;

      $Depotfile = &path("$P4_DEPOT/$line/", $file_dir, $file_name);
      $Clientfile = &path("$Client/$line/", $file_dir, $file_name);

      &insuredir(&dirname($Clientfile));

      if ($state eq "dead")
        {
          $Exists_in_depot = &depot_has($Clientfile);
          if ($Exists_in_depot)
            {
	      if (! -e $Clientfile)
                {
		  # Create a dummy one to remove (p4/ must be stateless)
                  #
		  if (! open(C, ">$Clientfile"))
                    {
                      print "$Myname: can't create \"$Clientfile\": $!.\n";
                      exit 1;
                    }
                  close C;
                }
              if ($sts = &s("$P4 delete '$Clientfile'", $Do)) { exit $sts; } $nc++;
	      if ($files) { $files .= "\001"; }
              $files .= "\t$Depotfile\t# Delete";
            }
        }
    }
  if ($nc > 0) { &p4submit("Deleting\n", $who, $time, $files, @change); }

  print "===adds/edits===\n";
  $nc = 0; undef $files;
  foreach $c (@change)
    {
      ($filerev, $time, $who, $state, $line, $branches, $prevrev, $options) = split(/$S/, $c);
      if ($line eq "$TRUNKLINE") { $line = "main"; }
      @filerev = split(/\//, $filerev);
      $rev = pop(@filerev);
      $file = join("/", @filerev);

      ($file_dir = &dirname($file)) =~ s/^$CVS_MODULE//;
      $file_dir =~ s/^\///;
      ($file_name = $file) =~ s%^.*/%%;

      $Depotfile = &path("$P4_DEPOT/$line/", $file_dir, $file_name);
      $Clientfile = &path("$Client/$line/", $file_dir, $file_name);

      $Rcsfile = "$file,v";
      if (! -f $Rcsfile)
        {
          $Rcsfile = &path("$CVS_MODULE/", $file_dir, "Attic/$file_name,v");
          if (! -f $Rcsfile)
            { print "$Myname: can't find RCS ,v file for \"$file\".\n"; exit 1; }
        }
              
      &insuredir(&dirname($Clientfile));

      # Note: we assume binary files never become text files or vice versa
      #

      if ($state ne "dead")
        {
          $Exists_in_depot = &depot_has($Clientfile);

          if ($Exists_in_depot)
            {
              if (($sts = &rm($Clientfile)) || ($sts = &s("$CO -p$rev '$Rcsfile' >'$Clientfile'", $Do)))
                { exit $sts; }

              if ($sts = &s("$P4 edit '$Clientfile'", $Do)) { exit $sts; } $nc++;
	      if ($files) { $files .= "\001"; }
              $files .= "\t$Depotfile\t# Edit";
            }
          else
            {
              if (($sts = &rm($Clientfile)) || ($sts = &s("$CO -p$rev '$Rcsfile' >'$Clientfile'", $Do)))
                { exit $sts; }

	      # This is where we decide if this pup is a text or a binary file.
              #
              # We used to just trust the CVS options to tell us when we have
              # a binary... but many times binary files get checked in to CVS
              # and nobody remembers to tell CVS! So.... we now try to be more
              # clever.
              if ($options =~ /[ob]/ || -B $Clientfile || &is_binary_file($Clientfile))
                { $type = "binary" }
              else
                { $type = "text"; }

              if ($options =~ /x/) { $type = "x$type"; }
              if ($type =~ /text/) { $type = "k$type"; }

              if ($sts = &s("$P4 add -t $type '$Clientfile'", $Do)) { exit $sts; } $nc++;
	      if ($files) { $files .= "\001"; }
              $files .= "\t$Depotfile\t# Add";
            }
        }
    }

  if ($nc > 0) { &p4submit($MSGS{$filerev}, $who, $time, $files, @change); }

  print "===branches===\n";
  $nc = 0; undef $files;
  foreach $c (@change)
    {
      ($filerev, $time, $who, $state, $line, $branches, $prevrev, $options) = split(/$S/, $c);
      if ($line eq "$TRUNKLINE") { $line = "main"; }
      if ($state ne "dead" && $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 = &path("$P4_DEPOT/$line/", $file_dir, $file_name);
          $Clientfile = &path("$Client/$line/", $file_dir, $file_name);

          foreach $branch (split(/:/, $branches))
            {
              $Depot_branchfile = &path("$P4_DEPOT/$branch/", $file_dir, $file_name);
              if ($sts = &s("$P4 integrate '$Depotfile' '$Depot_branchfile'", $Do)) { exit $sts; } $nc++;
	      if ($files) { $files .= "\001"; }
              $files .= "\t$Depot_branchfile\t# Branch";
              $Client_branchfile = &path("$Client/$branch/", $file_dir, $file_name);
            }
        } 
    }
  if ($nc > 0) { &p4submit("Branching\n", $who, $time, $files, @change); }
}


#  Expect messages from "p4d checkpoint"
#
$check_checkpoints = <<MSGS;
^command "checkpoint"\$
^p4d_admin> 
^: Checkpointing to 
^: Saving journal to 
^: Truncating 
MSGS
@check_checkpoints = split(/\n/, $check_checkpoints);

sub docheckpoint
{
  my($cmd) = "rsh $P4HOST -l $P4USER /u/p4/bin/p4d_admin port $P4PORTNUM rsh checkpoint 2>&1 </dev/null |";

  print "\n=== checkpoint after $do_change_num\n";

  if (! open(RSH, $cmd))
    {
      print "$Myname: can't open \"$cmd\": $!.\n";
      exit 1;
    }

  print "$Myname: $cmd\n";
  while (<RSH>)
    {
      &checkmsg($_, "p4d_admin rsh checkpoint", @check_checkpoints);
      print "$Myname: CHECKPOINT: $_";
    }
  close RSH;
} 


###### main starts here
#  

(@pwent) = getpwuid($<);
if ($#pwent < 7)
  {
    print STDERR "$Myname: can't get your passwd file entry.\n";
    exit 1;
  }
$Username = $pwent[0];

if ($CHECKPOINT_INTERVAL &&
      -x "/bin/domainname" &&
      `/bin/domainname` =~ /^netapp.com$/)
  { $Docheckpointing = 1; }
else
  { $Docheckpointing = 0; }

# option switch variables get defaults here...

$Metadata = "metadata";

# option switch variables get defaults here...

$Boolopt = 0;
$V = 1;
$Start_at_ch = 1;

while ($#ARGV >= 0)
  {
    if ($ARGV[0] eq "-boolopt")    { $Boolopt = 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; }
        $Start_at_ch = $ARGV[0]; shift; next;
      }
    elsif ($ARGV[0] eq "-help")
      { &help; }
    elsif ($ARGV[0] =~ /^-/) { &usage; }
    if ($Args ne "") { $Args .= " "; }
    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\": $!";

$Metadata = "$Convdir/metadata";
$Logmsgs  = "$Convdir/logmsgs";
$Changes  = "$Convdir/changes";
$Revmap   = "$Convdir/revmap";
$Client   = "$Convdir/p4";

$LOGNOTE = 1;

require "$Convdir/config";

if (! defined($P4PORT))
  { print "$Myname: no P4PORT in \"$Convdir/config\".\n"; exit 1; }
else
  { $ENV{"P4PORT"} = $P4PORT; }

($P4HOST = $P4PORT) =~ s/:.*//;
($P4PORTNUM = $P4PORT) =~ s/^.*://;

$P4USER = "p4";

$ENV{"P4CLIENT"} = $Convclient;

# These defaults can be overriden in the config file
#
#  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; }

#  Path the the p4 client command
#
if (! defined($P4)) { $P4 = "/usr/local/bin/p4"; }
if (! -x ($P4))
  { print "$Myname: No executable \"p4\" command at \"$P4\".\n"; exit 1; }

if ($Start_at_ch == 1)
  {
    #  We only do this to start (not when restarting from a chackpoint)
    #

    $P4CMD = "$P4 client -i";

    # Remove $Convdir/p4 if it already exists...
    # (This is where the client tree gets build)
    #
    &s("/bin/rm -rf '$Convdir/p4'");

    # Remove any revmap files...
    #  
    &s("/bin/rm -f $Revmap.dir $Revmap.pag");

    # Remove any existing client defined with this name.
    #
    &s("$P4 client -d $Convclient");
    if (! open(P4, "| $P4CMD"))
      {
        print "$Myname: open \"/usr/local/bin/p4 client -i\" failed: $!.\n";
        exit 1;
      }

    print P4 <<CLI;
Client: $Convclient

Root:   $Client

View:
        $P4_DEPOT/...   //$Convclient/...
CLI

    close P4;
    if ($?) { print "$Myname: \"$P4CMD\" failed to create client.\n"; exit 1; }
  }

#  Open the changes file (this is the genchamges output file)
#
if (! open(CHGS, "<$Changes"))
  { print "$Myname: can't open \"$Changes\": $!\n"; exit 1; }

#  Open the REVMAP database...
#
if (! dbmopen(REVMAP, $Revmap, 0666))
  { print "$Myname: can't dbmopen \"$Revmap\": $!\n"; exit 1; }

#  Open the log messages database
#
if (! dbmopen(MSGS, $Logmsgs, 0444))
  { print "$Myname: can't dbmopen \"$Logmsgs\": $!\n"; exit 1; }

$gather_change_num = $do_change_num = 0;
while (<CHGS>)
  {
    if (/^# ([0-9]+)$/)
      {
        $gather_change_num = $1;
        if ($do_change_num)
          {
            &dochange(@change);
            if (($CHECKPOINT_INTERVAL > 0) && (($do_change_num % $CHECKPOINT_INTERVAL) == 0))
              { if ($Docheckpointing) { &docheckpoint; } }
            $do_change_num = 0;
          }
	if ($gather_change_num < $Start_at_ch) { $gather_change_num = 0; next; }

        #  Clear the change
        #
        undef @change;
      }
    elsif ($gather_change_num)
      { push(@change, $_); $do_change_num = $gather_change_num; }
  }
if ($do_change_num) { &dochange(@change); }

#  I like closure...
#
dbmclose MSGS;
close CHGS;
dbmclose REVMAP;

if ($Docheckpointing) { &docheckpoint; }


#  This is the pre- -s capable version...
#  keeping it around a few revs for reference.
#
#  #  Eat the first line of CHGS; it should be a change number...
#  #
#  $_ = <CHGS>;
#  if (/^# ([0-9]+)$/)
#    { $change_num = $1; }
#  else
#    { die "first input line !~ /^# ([0-9]+)\$/...?"; }
#  
#  $n_changes_done = 0;
#  while (! eof CHGS)
#  { 
#    #  Clear the change
#    #
#    undef @change;
#  
#    #  Gather all the revs for this change...
#    #
#    while (<CHGS>)
#      {
#        if (/^# ([0-9]+)/) { $change_num = $1; last; }
#        chop;
#        push(@change, $_);
#      }
#  
#    #  And make it so...
#    #  
#    &dochange(@change);
#    $n_changes_done++;
#  
#    #  Time to checkpoint?
#    #
#    if (($CHECKPOINT_INTERVAL > 0) && (($n_changes_done % $CHECKPOINT_INTERVAL) == 0))
#      { if ($Docheckpointing) { &docheckpoint; } }
#  }