dochanges #16

  • //
  • guest/
  • matthew_rice/
  • util/
  • cvs2p4/
  • bin/
  • dochanges
  • View
  • Commits
  • Open Download .zip Download (35 KB)
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
  & eval 'exec perl -w -S $0 $argv:q'
  if 0;
#  THE PRECEEDING STUFF EXECS perl via $PATH
# -*-Fundamental-*-

require 5.000;

use POSIX;
use P4::Modules;

#  $Id: //guest/matthew_rice/util/cvs2p4/bin/dochanges#16 $
#
#  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.\$
^Change \\d+ renamed change \\d+ and 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);


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\": $!";

require "$Mydir/lib/util.pl";

$P4 = 'p4';
$Convclient = "";
$mod = '';
$DefaultClient = "cvs2p4";
($total_revs, $total_changes) = (0, 0);
($rev_cnt, $change_cnt) = (0, 0);
$partial_change = 0;

sub usage
{
  print <<_EOF_;
Usage: $Myname [ common options ] -f conversiondir
       $Myname [ common options ] [ -d -i -t -e end -r -l ] conversiondir
       $Myname -h

  common options:
    -a file      file listing directory root to abbreviate
    -b file      file listing the branch mappings
    -c client    use client spec 'client' (def. cvs2p4)
    -f           only place rlog(1) in file locations (fast conversion)
    -h           print this message
    -k killfile  stop cleanly if 'killfile' exists (def. \$HOME/cvs2p4.stop)
    -m modules   use P4::Modules file 'modules' to place files into depot
    -o omit      a 'p4 protect' style listing of directories to omit
    -p           report progress during conversion
    -q           be really quiet with output
    -s start     start at change group 'start' (or >1 w/-q to not restart)
    -v           verbose output (can use more than once)
    --warn-nobranchmap  when using -b, warn of missing maps (default is exit)
    --warn-nomap skip files that don't map into the depot (default is exit)
    --warn-omit  skip files that are in the omission list (default is exit)

  full conversion options:
    -d           produce donelog file in conversiondir
    -e end       stop after finishing change group 'end'
    -i           restart after last change in donelog (implies -d)
    -l           produce revlog file in conversiondir
    -r           produce revmap DB files in conversiondir (see caveats)
    -t           only import the first and head revisions
_EOF_
  exit $_[0];
}

#  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 or die "couldn't make $d: $!\n";
    }
}


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

sub verbose {
    my $level = @_ > 1 ? shift : 1;
    print @_, "\n" if $level <= $V;
}

sub p4 {
    my $cmd = shift;
    my @list = `$P4 $cmd`;
    if ($?) {
        verbose "p4 return: @{[$?>>8]}";
	die "error doing \"$P4 $cmd\"";
    }
    @list;
}

sub p4where
{
  my ($file) = @_;
  my $cl = $Convclient;

  my (@output) = p4 "where \Q$file\E";
  
  verbose 2, "p4 where \Q$file\E";
  my ($root) = grep /^Root:/, p4 "client -o \Q$cl\E";
  chomp @output;
  warn "warning: ambiguous mapping for $file\n" if @output > 1;
  pop @output while $output[$#output] =~ /^-/;
  my $output = pop @output;
  chomp $root;
  $root =~ s/^Root:\s*//;

  my ($depot, $client, $local) = split (/ (?:\/\/$cl|$root)\//, $output);
  $client = "//$cl/$client";
  $local = "$root/$local";

  ($depot, $client, $local);
}

# Hopefully, you don't have to call this ...
#
%REVERSEMAPS = ();
$openReverseLog = 0;

sub get_oldrev {
    my $filerev = join '/', @_;
    my $depot_br = undef;
    verbose 3, "looking for match to $filerev";
    if (!makeREVLOG && !makeREVMAP) {
        die "looking for a filerev for an old branch point but you\n",
            "have no revlog/map.  Please restart dochange at change 1.\n";
    } elsif (defined($REVERSEMAP{$filerev})) {
        $depot_br = $REVERSEMAP{$filerev};
        verbose 3, "found cached match of $depot_br for $filerev";
    } elsif ($makeREVMAP) {
        while (my ($p4, $cvs) = each %REVMAP) {
            ($br, $cvs) = split /$S/, $cvs;
            $REVERSEMAP{"$br/$cvs"} = $p4
			unless defined $REVERSEMAP{"$br/$cvs"};
	    verbose 4, "comparing:\n\t$br/$cvs\n\t$filerev";
            $depot_br = $p4, last if "$br/$cvs" eq $filerev;
        }
        verbose 3, "found revmap match of $depot_br for $filerev";
    } else {
        if (!$openReverseLog) {
            verbose 3, "opening $Revlog for reading old versions";
            open(REVERSELOG, "<$Revlog") || die "can't open read $Revlog: $!";
            $openReverseLog = 1;
        }
        while (my $line = <REVERSELOG>) {
            my ($p4, $br, $cvs) = split /$S/, $line;
            $REVERSEMAP{"$br/$cvs"} = $p4
			unless defined $REVERSEMAP{"$br/$cvs"};
	    verbose 4, "comparing:\n\t$br/$cvs\n\t$filerev";
            $depot_br = $p4, last if "$br/$cvs" eq $filerev;
        }
        verbose 3, "found revlog match of $depot_br for $filerev";
    }
    die "couldn't find a filerev for an old branch of $filerev\n"
        unless $depot_br;
    verbose 2, "get_oldrev returning: $depot_br";
    return $depot_br;
}

#  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; } }
  die "$Myname: checkmsg: *** $from *** $msg\n";
}  

# A temp file used to hold p4 command output
#
$tmpout = POSIX::tmpnam();

#  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, $wheres, @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);
  if ($V > 4) {
    verbose 5, "submitting:\n  ", join("\n", @files);
    verbose 5, "wheres:\n  ", join("\n  ", map {
				    $t = $$wheres{$_};
				    "$$t[1] $$t[0]\n    $_"
				} keys %$wheres);
  }

  #  Start the submit...
  #
  $p4submit = "$P4 submit -i >$tmpout 2>&1";
  verbose "$Myname: ...| $p4submit";
  if (! open(SUBMITW, "| $p4submit"))
    {
      die "$Myname: open \"| $p4submit\" failed: $!.\n";
    }
 
  $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 or die "couldn't print to $p4submit: $!\n";

  #  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)
    {
      verbose 2, "->SUBMITW:\t$line";
      print SUBMITW "\t$line\n" or die "couldn't print to $p4submit: $!\n";
    }

  $P2 = <<P2;

Files:
P2
  if ($V > 1) { my $vl; foreach $vl (split(/\n/, $P2)) { print "->SUBMITW: $vl\n"; } }
  print SUBMITW $P2 or die "couldn't print to $p4submit: $!\n";

  #  Insert the files list...
  #
  foreach $file (@files)
    {
      verbose 2, "->SUBMITW:\t$file";
      print SUBMITW "$file\n" or die "couldn't print to $p4submit: $!\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"))
    {
      die "$Myname: open \"<$tmpout\" failed: $!.\n";
    }

  #  Build %chrevs - the whole first field of the change line, indexed
  #  by the file path part.
  #
  my %chrev = ();
  foreach $chrev (@change)
    {
      my $revinfo = $chrev;
      chomp $revinfo;
      my ($rev) = split "$S", $revinfo;
      $rev =~ m%(.*)/[0-9\.]+$%;
      verbose 4, "chrev: $1 and $revinfo";
      $chrev{$1} = $revinfo;
    }

  while (<SUBMITR>)
    {
      verbose "SUBMITR->:$_";
      &checkmsg($_, "p4 submit", @check_submits);

      #  Now update the rev map for each Perforce rev we see...
      #
      if (/^(add|branch|delete|edit) (.*)#([0-9]+)$/)
        {
	  my $p4rev = $2."#".$3;
	  my ($chpath, $codeline) = @{$wheres->{$2}};
          my $cvsrev = $chrev{$chpath} ||
                             die "couldn't find CVSREV info for $chpath\n";
	  $codeline = $TRUNKLINE if $codeline eq "main";
          verbose 4, "revlog: $codeline and $chpath";
	  $REVMAP{$p4rev} = join("$S", $codeline, $cvsrev)
                                        if $makeREVMAP;
	  print REVLOG join("$S", $p4rev, $codeline, "$cvsrev\n")
                or die "couldn't print to REVLOG: $!\n" if $makeREVLOG;
        }          
      if ($_ =~ /^Change (\d+) submitted\.$/) { $changenum = $1; }
      if ($_ =~ /^Change \d+ renamed change (\d+) and submitted\.$/)
        {
          $changenum = $1;
          warn "warning: change number was renumbered (other users are",
                "probably active\n";
        }
    } 
  close SUBMITR;

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

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

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 or die "couldn't print to $p4changef: $!\n";

  #  Insert the log message. Again.
  #
  foreach $line (@msg)
    {
      verbose 2, "->CHANGEW:\t$line";
      print CHANGEW "\t$line\n" or die "couldn't print to $p4changef: $!\n";
    }
  close CHANGEW or die "couldn't close $p4changef: $!\n";

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

  while (<CHANGER>)
    {
      verbose "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;
}

#  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 \Q$clientfile\E 2>&1";
  verbose "$Myname: $p4files |...\n";
  if (! open(FILES, "$p4files |"))
    {
      die "$Myname: open \"$p4files |\" failed: $!.\n";
    }
  $line = <FILES>;
  close FILES;
  if ($line =~ /- no such file\(s\)\./
        || $line =~ / - file\(s\) not in client view\./
        || $line =~ / - delete change /)
    { return 0; }
  else
    { return 1; }
}

# This function is similar to the one in ckclientview.
# If a change is required, determine if the same change
# should be made there.
#
sub prepFiles {
    my ($file, $line, $p4wheres) = @_;

    $file =~ s|^/?|/|;
    my $file_dir = &dirname($file);
    (my $file_name = $file) =~ s%^.*/%%;
    my ($Depotfile, $Clientfile) = ('', '');
    my $abbr = '';
    
    for my $a (@ABBRS) {
        $abbr = $a, last if $file =~ /^$a/;
    }
    if (%BRANCHES and !$BRANCHES{$line} and $line ne 'main') {
	warn "warning: using branch mappings but no mapping found for $line. ",
		"Is it new?\nfile checked: $file\n";
        die "can't continue\n" if !$warnOnNobranchmap;
	warn "Continuing with original name\n";
    }
    my $newbr = $BRANCHES{$line} || $line;
    if ($newbr =~ m|^//|) {
            $Depotfile = $newbr . "$file_dir/$file_name";
            if ($Convclient ne $DefaultClient) {
	        $Clientfile = &p4where($Depotfile);
            } else {
	        $Clientfile = $Depotfile;
                $Clientfile =~ s|^//|$Client/|;
            }
    } elsif ($mod) {
	    $Depotfile = $mod->where($newbr, "$file_dir/$file_name");
	    ($Clientfile = $Depotfile) =~ s|^//|$Client/|;
    } else {
	    $Clientfile = &path("$Client$Depot/" . $newbr,
 				$file_dir, $file_name);
	    ($Depotfile) = &p4where($Clientfile);
    }
    unless ($Depotfile) {
	warn "couldn't map $newbr and ",
		"$file_dir/$file_name into the depot... skipping\n";
        die "can't continue\n" if !$warnOnNomap;
        return (undef, undef, undef);
    }
    if ($abbr) {
        $newbr =~ s|.*/|/|;
        verbose 2, "removing abbrevation $abbr after $newbr";
        $Depotfile  =~ s|(.*$newbr.*?)$abbr|$1/|;
        $Clientfile =~ s|(.*$newbr.*?)$abbr|$1/|;
    }
    for my $o (@OMIT) {
	verbose 3, "checking $Depotfile against omission '$o'";
	if ($Depotfile =~ /$o/) {
	    warn "$Depotfile is in the omission list '$o'... skipping\n";
            die "can't continue\n" if !$warnOnOmit;
	    return (undef, undef, undef);
	}
    }
    verbose 4, "mapped $file on $line into $Depotfile and $Clientfile";
    $p4wheres->{$Depotfile} = [ &path("", $file_dir, $file_name), $line ];
    &insuredir(&dirname($Clientfile));

    ($Depotfile, $Clientfile, "$CVS_MODULE$file,v");
}

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

  verbose 0, "\n========== change group $do_change_num ===============";

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

  # Do the revisions...
  #
  my ($tstart, $tmid, $tstop) = (time(), 0, 0);
  $cvsid = '';
  if ($partial_ch <= 1) {
    verbose 0, "=== deletes $do_change_num.1 ===";
    $nc = 0; undef $files;
    %p4wheres = ();
    foreach $c (@change)
    {
      ($filerev, $time, $who, $state, $line, $branches,
       $prevrev, $options, $md5msg) = split(/$S/, $c);
      next if $branches =~ /^-:/;
      if ($line eq "$TRUNKLINE") { $line = "main"; }
      @filerev = split(/\//, $filerev);
      $rev = pop(@filerev);
      $file = join("/", @filerev);
      if ($state eq "dead" && $onlyTips && $branches eq "-"
	  && !($rev =~ m/\.1$/ || $rev eq $TIPS{"$file$S$line"})) {
	  verbose 1,
		  "$Myname: skipping non-tip revision \"$rev\" for $file",
		  " on branch \"$line\"";
	  $rev_cnt++;
	  next;
      }

      my ($Depotfile, $Clientfile) = prepFiles($file, $line, \%p4wheres);
      next unless $Depotfile;

      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"))
                    {
                      die "$Myname: can't create \"$Clientfile\": $!.\n";
                    }
                  close C;
                }
              if ($sts = &s("$P4 delete \Q$Clientfile\E $DEVNULL", $Do)) { exit $sts; } $nc++;
	      $rev_cnt++;
      	      $cvsid = $rev;
	      if ($files) { $files .= "\001"; }
              $files .= "\t$Depotfile\t# Delete";
            }
        }
    }
    $tmid = time();
    if ($nc > 0) {
	my $msg = "Deleting\n";
	$msg .= "\nCVSID: $cvsid\n" if $nc == 1;
	&p4submit($msg, $who, $time, $files, \%p4wheres, @change);
    }
    $tstop = time();
    print DONELOG "$do_change_num.1 $tstart $tmid $tstop $nc\n"
                or die "can't write to DONELOG: $!\n" if $doLog;
    verbose 0, "=== deletes done";
  }

  if ($partial_ch <= 2) {
    verbose 0, "=== adds/edits $do_change_num.2 ===";
    $tstart = time();
    $nc = 0; undef $files;
    %p4wheres = ();
    foreach $c (@change)
    {
      ($filerev, $time, $who, $state, $line, $branches,
       $prevrev, $options, $md5msg) = split(/$S/, $c);
      next if $branches =~ /^-:/;
      if ($line eq "$TRUNKLINE") { $line = "main"; }
      @filerev = split(/\//, $filerev);
      $rev = pop(@filerev);
      $file = join("/", @filerev);
      if ($state ne "dead" && $onlyTips && $branches eq "-"
	  && !($rev =~ m/\.1$/ || $rev eq $TIPS{"$file$S$line"})) {
	  verbose 1,
		  "$Myname: skipping non-tip revision \"$rev\" for $file",
		  " on branch \"$line\"";
	  $rev_cnt++;
	  next;
      }

      my ($Depotfile, $Clientfile, $Rcsfile) =
				prepFiles($file, $line, \%p4wheres);
      next unless $Depotfile;

      -f $Rcsfile or $Rcsfile =~ s|(.*)/(.*)|$1/Attic/$2|;
      -f $Rcsfile or die "$Myname: can't find RCS ,v file for \"$file\".\n";

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

      if ($state ne "dead")
        {
	  # 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.
          my $type = "text";
          $type = "binary" if $options =~ /[ob]/ || -B $Clientfile ||
                                &is_binary_file($Clientfile);
          if ($options =~ /x/) { $type = "x$type"; }
          if ($type =~ /text/) { $type = "k$type"; }

          my $op = &depot_has($Clientfile) ? "edit" : "add";

          if (($sts = &rm($Clientfile)) ||
              ($sts = &co("$CO -p$rev \Q$Rcsfile\E >\Q$Clientfile\E $DEVNULL2",
                                $Do, $Clientfile)) ||
              ($sts = &s("$P4 $op -t $type \Q$Clientfile\E $DEVNULL", $Do))) {
                exit $sts;
          }
          $nc++;
	  $rev_cnt++;
	  $cvsid = $rev;
	  if ($files) { $files .= "\001"; }
          $files .= "\t$Depotfile\t# \u$op";
        }
    }
    $tmid = time();
    if ($nc > 0) {
	my $msg = $MSGS{$md5msg};
	$msg .= "\nCVSID: $cvsid\n" if $nc == 1;
	&p4submit($msg, $who, $time, $files, \%p4wheres, @change);
    }
    $tstop = time();
    print DONELOG "$do_change_num.2 $tstart $tmid $tstop $nc\n"
                or die "can't write to DONELOG: $!\n" if $doLog;
    verbose 0, "=== adds/edits done";
  }

  verbose 0, "=== branches $do_change_num.3 ===";
  $tstart = time();
  $nc = 0; undef $files;
  %p4wheres = ();
  foreach $c (@change)
    {
      ($filerev, $time, $who, $state, $line, $branches,
       $prevrev, $options, $md5msg) = split(/$S/, $c);
      my $oldrevline = $line;
      if ($line eq "$TRUNKLINE") { $line = "main"; }
      if ($state ne "dead" && $branches ne "-")
        {
          @filerev = split(/\//, $filerev);
          $rev = pop(@filerev);
          $file = join("/", @filerev);

          my ($Depotfile, $Clientfile) = prepFiles($file, $line, \%p4wheres);
          next unless $Depotfile;

          my $need_oldrev = 0;
          foreach $branch (split(/:/, $branches))
            {
              $need_oldrev = 1, next if $branch eq "-";
	      my ($Depot_branchfile, $Client_branchfile) =
                                prepFiles($file, $branch, \%p4wheres);
              next unless $Depot_branchfile;

              if ($need_oldrev) {
                  $Depotfile = get_oldrev($oldrevline, $file, $rev);
                  my $tmpDepotfile = $Depotfile;
                  $tmpDepotfile =~ s/#.*//;
                  $p4wheres{$tmpDepotfile} = &path("", $file_dir, $file_name);
              }
              if ($sts = &s("$P4 integrate \Q$Depotfile\E \Q$Depot_branchfile\E $DEVNULL", $Do)) { exit $sts; } $nc++;
	      $rev_cnt++;
	      if ($files) { $files .= "\001"; }
	      $cvsid = $rev;
              $files .= "\t$Depot_branchfile\t# Branch";
            }
        } 
    }
  $tmid = time();
  if ($nc > 0) {
	my $msg = "Branching\n";
	$msg .= "\nCVSID: $cvsid\n" if $nc == 1;
	&p4submit($msg, $who, $time, $files, \%p4wheres, @change);
  }
  $tstop = time();
  print DONELOG "$do_change_num.3 $tstart $tmid $tstop $nc\n"
                or die "can't write to DONELOG: $!\n" if $doLog;
  verbose 0, "=== branches done";

  $partial_ch = 0;
}


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

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

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

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


###### main starts here
#  

(@pwent) = getpwuid($<);
if ($#pwent < 7)
  {
    die "$Myname: can't get your passwd file entry.\n";
  }
$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";

# Used if the user doesn't specify a client spec of modules file.
$Depot    = "/depot";

# option switch variables get defaults here...

$Boolopt = 0;
$V = 0;
$Start_at_ch = 1;
$End_at_ch = 999999999;	#Magic number are bad but ...
$makeREVMAP = 0;
$makeREVLOG = 0;
$onlyTips = 0;
$Modules = '';
$help = 0;
$afile = '';
@ABBRS = ();
$bfile = '';
%BRANCHES = ();
$progress = 0;
$DEVNULL2 = "2>/dev/null";
$DEVNULL = ">/dev/null";
$incremental = 0;
$doLog = 0;
$ofile = 0;
$warnOnOmit = $warnOnNobranchmap = $warnOnNomap = 0;
$quick = 0;
$quiet = 0;
$killfile = "$ENV{HOME}/cvs2p4.stop";

use Getopt::Long;
GetOptions(
	"abbreviations=s"   => \$afile,
	"branches=s"        => \$bfile,
	"client=s"          => \$Convclient,
	"dolog"             => \$doLog,
	"end=i"             => \$End_at_ch,
	"fast"              => \$quick,
	"help"              => \$help,
	"incremental"       => \$incremental,
	"killfile=s"        => \$killfile,
	"log"               => \$makeREVLOG,
	"modules=s"         => \$Modules,
	"omit=s"            => \$ofile,
	"progress"          => \$progress,
	"quiet+"            => \$quiet,
	"revmap"            => \$makeREVMAP,
	"start=i"           => \$Start_at_ch,
	"tip"               => \$onlyTips,
	"verbose+"          => \$V,
	"warn-nobranchmap"  => \$warnOnNobranchmap,
	"warn-nomap"        => \$warnOnNomap,
	"warn-omit"         => \$warnOnOmit,
) || die usage(1);
$help && usage(0);

$doLog = 1 if $incremental;
$Depot = "" if $Convclient || $Modules;
$Convclient = $DefaultClient unless $Convclient;
$V = -$quiet if $quiet;
$DEVNULL = "", $DEVNULL2 = "" if $V > 0;

$Convdir = shift || usage(1);
$mod = P4::Modules->new($Modules) if $Modules;


warn "incremental without a revlog/map is dangerous\nSee FAQ\n"
        if $incremental && !($makeREVLOG || $makeREVMAP);
die "You can't specify incremental and a start group\n"
	if $incremental && $Start_at_ch > 1;
die "You can't specify both the quick and tip imports together\n"
	if $quick && $onlyTips;
die "Ending change group must be higher or equal to the starting change group\n"
	unless $Start_at_ch <= $End_at_ch;
verbose "Watching for killfile: $killfile";

chdir $Convdir || die "$Myname: can't chdir \"$Convdir\": $!";
$Convdir = `/bin/pwd`; chop $Convdir; $Convdir =~ s|/*$||;
chdir $Here || die "$Myname: can't chdir \"$Here\": $!";

$Metadata = "$Convdir/metadata";
$Logmsgs  = "$Convdir/logmsgs";
$Tips     = "$Convdir/tips";
$Changes  = "$Convdir/changes";
$Donelog  = "$Convdir/donelog";
$Revlog   = "$Convdir/revlog";
$Revmap   = "$Convdir/revmap";
$Client   = "$Convdir/p4";


$LOGNOTE = 1;

require "$Convdir/config";

if (! defined($P4PORT))
  { die "$Myname: no P4PORT in \"$Convdir/config\".\n"; }
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 and rlog commands
#
if (! defined($CO)) { $CO = "/usr/local/bin/co"; }
if (! -x ($CO))
  { die "$Myname: No executable \"co\" command at \"$CO\".\n"; }

if (! defined($RLOG)) { $RLOG = "/usr/local/bin/rlog"; }
if (! -x ($RLOG))
  { die "$Myname: No executable \"rlog\" command at \"$RLOG\".\n"; }

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

if ($incremental && open(RDONELOG, "<$Donelog")) {
    my $change = 0;
    while (<RDONELOG>) {
        ($change) = split / /;
    }
    close RDONELOG;
    if ($change) {
        ($Start_at_ch, $partial_ch) = split /\./, $change;
        $partial_ch++;
        $Start_at_ch++, $partial_ch = 0 if $partial_ch > 3;
    }
}
verbose "starting at Change $Start_at_ch and partial $partial_ch";

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 $Revlog $Revmap.dir $Revmap.pag $Donelog");

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

	my $Map = '';
    	for my $dview (split /\s+/, $P4_DEPOTS) {
            (my $cview = $dview) =~ s|^//|//$Convclient/|;
            $Map .= "\t$dview/... $cview/...\n";
    	} 
        print P4 <<CLI;
Client: $Convclient

Root:   $Client

View:
        $Map
CLI

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

#  Read in the abbreviation and branch mappings
#
if ($afile) {
    open ABBRS, "<$afile" or die "can't open $afile: $!\n";
    while (<ABBRS>) {
        chomp;
        next if /^#/;
        next if /^\s*$/;
        s|^/?|/|;
        s|/?$|/|;
        verbose "found abbreviation: $_";
        push @ABBRS, $_;
    }
    close ABBRS;
}  

if ($bfile) {
    open BRANCHES, "<$bfile" or die "can't open $bfile: $!\n";
    while (<BRANCHES>) {
        chomp;
        next if /^#/;
        next if /^\s*$/;
        my ($p4branch, $cvsbranch) = split /\s+/;
        $p4branch =~ s|/$||;
        $p4branch =~ s|^/|| unless $p4branch =~ m|^//|;
        $cvsbranch = $p4branch unless $cvsbranch;
        verbose "mapping branch $cvsbranch to $p4branch";
        $BRANCHES{$cvsbranch} = $p4branch;
    }
    close BRANCHES;
}  

# Read in the omission list.
if ($ofile) {
    open OMIT, "<$ofile" or die "can't open $ofile: $!\n";
    while (<OMIT>) {
        chomp;
        next if /^#/;
        next if /^\s*$/;
        verbose "found omission: $_";
	s/\*/[^\/]*/g;
	s/\.\.\./.*/g;
        verbose 2, "converted to regex: $_";
        push @OMIT, $_;
    }
    close OMIT;
}

if (! dbmopen(TIPS, $Tips, 0444))
  { die "$Myname: can't dbmopen \"$Tips\": $!\n"; }

if ($quick)
  {
    my @files = ();
    if ($progress)
      {
        $total_revs = keys %TIPS;
        $total_changes = int($total_revs/$QUICK_ADD_COUNT);
        $total_changes += $total_revs % $QUICK_ADD_COUNT ? 1 : 0;
	verbose -2, "expecting $total_revs new revisions in ",
	            "$total_changes change groups";
      }
    while (my ($file_line, $tip) = each %TIPS)
      {
	my ($file, $line) = split /$S/, $file_line;
	($file_dir = &dirname($file)) =~ s%^/%%;
	($file_name = $file) =~ s%^.*/%%;
	verbose 2, "checking: $file_dir";
	verbose 2, "checking: $file_name";
	my ($Depotfile, $Clientfile, $Rcsfile) =
					prepFiles($file, $line, \%p4wheres);
	next unless $Depotfile;

	-f $Rcsfile or $Rcsfile =~ s|(.*)/(.*)|$1/Attic/$2|;
	-f $Rcsfile or die "$Myname: can't find RCS ,v file for \"$file\".\n";

	$rev_cnt++;
	unless (&depot_has($Clientfile))
	  {
	    &rlog("$RLOG \Q$Rcsfile\E >\Q$Clientfile\E", $Do, $Clientfile);
	    unless (@files)
              {
	        open(P4, "|$P4 -x - add -t text") or die "can't open $P4\n";
	      }
	    print P4 "$Clientfile\n" or die "couldn't print to p4 add: $!\n";
            push @files, $Depotfile;
	  }
        if (@files == $QUICK_ADD_COUNT)
 	  {
	    close P4 or die "can't close $P4\n";
	    if ($?)
	      {
		die "$P4 add exited badly\n";
	      }
            &p4submit("quick import\n", "cvs2p4", time(),
		      "\t" . join("\t# Add\001\t", @files) . "\t# Add",
		      {}, ());
	    @files = ();
	    if ($progress)
	      {
	        $change_cnt = int($rev_cnt / $QUICK_ADD_COUNT);
	        verbose -2, "rev status: $rev_cnt of $total_revs";
	        verbose -2, "change status: $change_cnt of $total_changes";
	      }
	  }
	  if (-e $killfile)
	    {
	      verbose 0, "$Myname: stopping because $killfile present";
 	      last;
	    }
      }
      if (@files)
	{
	  close P4 or die "can't close $P4\n";
	  if ($?)
	    {
	      die "$P4 add exited badly\n";
            }
          &p4submit("quick import\n", "cvs2p4", time(),
		    "\t" . join("\t# Add\001\t", @files) . "\t# Add",
		    {}, ());
        }
	if ($progress)
	  {
	    $change_cnt = int($rev_cnt / $QUICK_ADD_COUNT);
            $change_cnt += $total_revs % $QUICK_ADD_COUNT ? 1 : 0;
	    verbose -2, "rev status: $rev_cnt of $total_revs";
	    verbose -2, "change status: $change_cnt of $total_changes";
	  }
  }
else
  {
    
    $ch = $Changes;
    if ($progress)
      {
	$cnt = 1;
	while (-e $Changes)
	  {
	    open CHGS, "<$Changes"
		or die "$Myname can't open \"$Changes\": $!\n";
	    while (<CHGS>)
	    {
	      /^#/ && do {
                $total_changes++;
                $change_cnt++ if $total_changes < $Start_at_ch;
                next;
              };
	      my @a = split /$S/;
	      my ($del, $add, $int) = (0, 0, 0);
	      if ($a[3] eq 'dead') {
		    $del = 1 unless $a[6] eq '-';
	      } else {
		    $add = 1 unless $a[5] =~ /^-:/;
		    $a[5] =~ s/^-://;
	            $int = 1 + ($a[5] =~ tr/:/:/) if $a[5] ne "-";
	      }
	      $total_revs += ($del + $add + $int);
              if ($total_changes < $Start_at_ch) {
		$del = 0 if $partial > 1;
		$add = 0 if $partial > 2;
	        $rev_cnt += ($del + $add + $int);
              }
	    }
	    close CHGS or die "can't close $Changes: $!\n";
	    $Changes  = $ch . "." . ++$cnt;
	}
	verbose -2, "expecting $total_revs new revisions in ",
	                "$total_changes change groups";
	$Changes = $ch;
      }
    
    #  Open the REVLOG file and/or REVMAP database...
    #
    if ($makeREVLOG) {
      if (! open(REVLOG, ">>$Revlog"))
        { die "$Myname: can't open \"$Revlog\": $!\n"; }
      # Turn of buffering so that get_oldrev will be able to get all info.
      my $oldfh = select(REVLOG); $| = 1; select($oldfh);
    }
    if ($makeREVMAP) {
      if (! dbmopen(REVMAP, $Revmap, 0666))
        { die "$Myname: can't dbmopen \"$Revmap\": $!\n"; }
    }
    if ($doLog) {
      if (! open(DONELOG, ">>$Donelog"))
        { die "$Myname: can't open \"$Donelog\": $!\n"; }
      my $oldfh = select(DONELOG); $| = 1; select($oldfh);
    }
    
    #  Open the log messages database
    #
    if (! dbmopen(MSGS, $Logmsgs, 0444))
      { die "$Myname: can't dbmopen \"$Logmsgs\": $!\n"; }

    $cnt = 1;
    while (-e $Changes)
    {
      if (! open(CHGS, "<$Changes"))
        { die "$Myname: can't open \"$Changes\": $!\n"; }
      $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 ($progress)
	    	    {
		      $change_cnt++;
	              verbose -2, "rev status: $rev_cnt of $total_revs";
	              verbose -2, "change status: $change_cnt of $total_changes";
	    	    }
                }
	      if ($gather_change_num < $Start_at_ch) { $gather_change_num = 0; next; }
      
	      my $stop_msg = -e $killfile ? "$killfile present" : "";
	      if ($gather_change_num > $End_at_ch) {
	          $stop_msg = "last requested change group processed" ;
	      }
	      if ($stop_msg) {
	          verbose 0, "$Myname: stopping because $stop_msg";
 	          last;
	      }
      
              #  Clear the change
              #
              undef @change;
            }
          elsif ($gather_change_num)
            { push(@change, $_); $do_change_num = $gather_change_num; }
        }
      if ($do_change_num)
        {
	  &dochange(@change);
  	  if ($progress)
    	    {
	      $change_cnt++;
              verbose -2, "rev status: $rev_cnt of $total_revs";
              verbose -2, "change status: $change_cnt of $total_changes";
    	  }
        }
      if ($Docheckpointing) { &docheckpoint; }
  
      close CHGS;
      $Changes = $ch . "." . ++$cnt;
    }
    dbmclose MSGS;
    close DONELOG if $doLog;
    close REVLOG if $makeREVLOG;
    dbmclose REVMAP if $makeREVMAP;

  }
dbmclose TIPS;

verbose 0, "dochanges is finished";
# Change User Description Committed
#22 883 Matthew Rice Matt's cvs2p4 1.2.36 release
#21 882 Matthew Rice Matt's cvs2p4 1.2.35 release
#20 881 Matthew Rice Matt's cvs2p4 1.2.34 release
#19 880 Matthew Rice Matt's cvs2p4 1.2.33 release
#18 879 Matthew Rice Matt's cvs2p4 1.2.32 release
#17 878 Matthew Rice Matt's cvs2p4 1.2.31 release
#16 877 Matthew Rice Matt's cvs2p4 1.2.30 release
#15 876 Matthew Rice Matt's cvs2p4 1.2.29 release
#14 875 Matthew Rice Matt's cvs2p4 1.2.28 release
#13 874 Matthew Rice Matt's cvs2p4 1.2.27 release
#12 873 Matthew Rice Matt's cvs2p4 1.2.26 release
#11 872 Matthew Rice Matt's cvs2p4 1.2.25 release
#10 871 Matthew Rice Matt's cvs2p4 1.2.24 release
#9 870 Matthew Rice Matt's cvs2p4 1.2.23 release
#8 869 Matthew Rice Matt's cvs2p4 1.2.22 release
#7 868 Matthew Rice Matt's cvs2p4 1.2.21 release
#6 867 Matthew Rice Matt's cvs2p4 1.2.20 release
#5 866 Matthew Rice Matt's cvs2p4 1.2.19 release
#4 865 Matthew Rice Matt's cvs2p4 1.2.18 release
#3 864 Matthew Rice Matt's cvs2p4 1.2.17 release
#2 863 Matthew Rice Matt's cvs2p4 1.2.16 release
#1 862 Matthew Rice Branched off of Richard's 1.2.15 release.
//guest/richard_geiger/utils/cvs2p4/bin/dochanges
#8 421 Richard Geiger Fix to the fix by Thomas Quinot.
#7 416 Richard Geiger Pull in Thomas Quinot <[email protected]>'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)