# -*-perl-*-
#
#   Copyright (c) 1996 Network Appliance, Inc.
#   Copyright (c) 2002 PiroNet NDH AG
#
#   You may distribute under the terms of the Artistic License, as
#   specified in the README file included in the cvslines
#   distribution.
#
#   Original Author: Richard Geiger for Network Appliance, Inc.
#   Adaption to cvs >=1.11 by Juergen Jatzkowski and Ingo Rockel
#
#  $Id: cvslines-check,v 1.2 2002/04/19 13:26:39 irockel Exp $

#  This file is used by "cvslines".
#  It is not intended for standalone execution.

#  Notes:
#    
#  - We never try to change updates to branch tags that would create a
#    new RCS branch to check-ins on the tip of an existing RCS branch,
#    even when there are no apparent instances of lines with revisions
#    on the tip of the existing branch. This would seem to reduce
#    unnecessary RCS branching, but we have no assurance that some
#    other line that's not active in the cvslines.config file is
#    sitting on the tip of the existing branch. Also, I can't imagine
#    how the tree would have gotton ito this state in the first place,
#    if nothing really occupies the existing branch.
#
#  - We *do* special-case the situation where a file was added via a
#    cvs import command, and a branched line was created before any
#    local commits have been done. In this case, the file is really
#    undiverged, though it looks to CVS as if it has (i.e., the
#    current rev for a head would be "1.1", but for undiverged
#    branched would be "1.1.1.1". We detect this case, and spoof CVS
#    as if the branch tage was "1.1.0.2", so that we can forstall
#    divergence if the update should also be applied to the head. This
#    also has the effect of having commits that *aren't* wanted on the
#    head being commited to "1.1.2.1" (etc), rather than the (uglier)
#    "1.1.1.1.2.1". You'll still get the uglies if the head has
#    advanced beyond "1.1", but I don't want the extra complication of
#    trying to spoof that at this point.
#

$Logfrom = "check";

sub sticky_load
{
  my $line;
  my $ans;

  if (! open(STICKY, "<$Stickyans"))
    { $Stickyans = ""; } # revery to unsticky mode
  else
    {
      while (<STICKY>)
        {
	  chop;
	  ($line, $ans) = split(/ /, $_);
          $Stickyans{$line} = $ans;
        }
      close STICKY;
    }
}


sub sticky_store
{
  my $line;

  if (! open(STICKY, ">$Stickyans"))
    {
      print TTYO "$Mynamebase: can't write \"$Stickyans\": $!\n";
      if (! unlink($Stickyans))
        {
          print TTYO "$Mynamebase: can't remove \"$Stickyans\": $!\n";
          return 1;
        }
    }
  else
    {
      foreach $line (sort(keys(%Stickyans)))
        { print STICKY "$line $Stickyans{$line}\n"; }
      close STICKY;
    }
}


sub is_branch
{
  my ($rev) = @_;
  return $rev =~ /\.0\.[0-9]+$/;
}


#  Given a branch tag revision, return the RCS branch number
#  for revisions on the branch.
#
sub branch_t
{
  my ($rev) = @_;
  $rev =~ /^(.+)\.0\.([0-9]+)$/;
  $rev = "$1.$2";
}


#  Given an RCS revision, return its branch number.
#
sub branch
{
  my ($rev) = @_;
  $rev =~ s/\.[0-9]+$//;
  return $rev;
}


sub nextrev
{
  my ($specrev) = @_;
  my $base;
  my $rev;

  if ($specrev =~ /^(.+)\.0\.([0-9]+)$/)
    {
      $base = "$1.$2"; $rev = "1";
      while (defined($RCS_Revs{"$base.$rev"})) { $rev++; }
      return "$base.$rev";
    }
  elsif ($specrev =~ /^(.+)\.([0-9]+)$/)
    {
      $base = $1; $rev = $2; $rev++;
      return "$base.$rev";
    }
  else
    { &noteNbail("nextrev(): internal error: malformed \$specrev \"$specrev\"."); }
}

#  This was added after converting all
#
#    foreach $line_name (keys(%state))    's to
#    foreach $line_name (@CVS_lines)      's
#
#  The latter is preferred, because the order in which the lines will
#  be processed is deterministic (according to the order in which they
#  appear in the cvslines.config file).
#
#  I added this assertion just to help convince myself that they
#  really are equivalent. The assert can be removed at some point
#  if we've never tripped it and are concerned about performance.
#
sub assert_keys
{
  my @K, @L;
  my $k, $l;

  @K = sort(keys(%state));
  @L = sort(@CVS_Lines);

  if ($#K != $#L) { die "assert_keys number K = $#K L = $#L"; }

  while ($#K)
    {
      $k = pop(@K); $l = pop(@L);
       if ($k ne $l) { die "assert_keys value k =<$k> l =<$l>"; }
    }
}


#  This is a debug aid...
#
sub dump_state
{
  print TTYO "\n========== states:\n";
  &assert_keys;
  foreach $line_name (@CVS_Lines)
    { print TTYO " $line_name :: $state{$line_name}\n"; }
  print TTYO "\n\n";
}


sub notify_re_tagswap
{
  my ($this_rev) = @_;
  my $this_branch = $this_rev;

  $this_branch =~ s/\.[0-9]+$//;

  print TTYO <<LIT;

NOTE:

  This check-in would normally (i.e., without $Mynamebase) cause a
  new RCS branch to be created.

  But, since you also want to apply the revision to all the other
  non-branched lines that share revision "$this_rev", it will be
  better to apply the change as a new revision to the "$this_branch"
  branch, and then update the branch tags for other lines.  This will
  help minimize actual divergence for this file in lines that don't
  need to diverge yet. This is reflected in the commit plan shown
  below.
LIT
}


#  Any parameters from cvslines-commit must be passed through the
#  environment, 'cause of the cvs commitinfo interface.
#
$Verbose   = $ENV{"CVSLINES_VERBOSE"};
$Noconfirm = $ENV{"CVSLINES_NOCONFIRM"};
$Showall   = $ENV{"CVSLINES_SHOWALL"};
$Stickyans = $ENV{"CVSLINES_STICKYANS"};
$Nolgroups = $ENV{"CVSLINES_NOLGROUPS"};

#  See if we're disabled (used by cvslines_commit to guard against
#  rechecks, or by users who want us to step aside).
#
if (defined($ENV{"CVSLINES_NOCHECK"}))
  {
    &log("CVSLINES_NOCHECK");
    exit 0;
  }

$here = `/bin/pwd`; chop $here;

if (! &openTTY())
  {
    &log("&openTTY() failed");  # assume it's from a remote checkin
    exit 0;
  }

if (! &module_config($here))
  { &noteNbail("no $Mynamebase.config file for this commit."); }

$CVS = "$here/CVS";

#  Check the interlock that insures the user is running via
#  "$Mynamebase commit"...
#
if (! defined($ENV{"CVSLINES_PHASE0"}))
  {
    if ($Users{$Username} eq "off") { exit 0; }
    if (! ($Users{ALL} eq "on" || $Users{$Username} eq "on")) { exit 0; }

    print TTYO <<LIT;
$Mynamebase:

  Commits for this module should be done via the "$Mynamebase commit"
  command.

  To force a "cvs commit" command from this module without $Mynamebase
  commit checks, run cvs with \$CVSLINES_NOCHECK set in the environment.

LIT
    exit 1;
  }


#  Check for .disable file...
#
if (-f $Configpath_Disable)
  {
    system("/bin/cat $Configpath_Disable");
    exit 1;
  }
    

if (defined($ENV{"CVSLINES_BATSEL"}))
  {
    my $def;
    my $exc;
    my @batsellines;
    my $batsel;
    my $line;
    my $excline;

    $Batsel = 1;
    $batsel = $ENV{"CVSLINES_BATSEL"};

    # Now make sure any names lines are valid & set up %Select_ans
    #
    if ($batsel =~ /^-all/)
      {
        $batsel =~ s/^-all-{0,1}//;
        $def = "y"; $exc = "n";
        @batsellines = split(/-/, $batsel);
      }
    elsif ($batsel =~ /^-only/)
      {
        $batsel =~ s/^-only\+{0,1}//;
        $def = "n"; $exc = "y";
        @batsellines = split(/\+/, $batsel);
      }
    else { die "$Mybasename: internal error: bad CVSLINES_BATSEL value \"$batsel\"." }

    foreach $line (@batsellines)
      {
        if (! defined($CVS_Lines_Spec{$line}))
          {
            print TTYO "$Mynamebase: unknown line \"$line\".\n";
            exit 1;
          }
      }

    foreach $line (@CVS_Lines)
      {
        $Select_ans{$line} = $def;
        foreach $excline (@batsellines)
          { if ($excline eq $line) { $Select_ans{$line} = $exc; } }
      }
  }
else
  {
    $Batsel = 0;
    if ($Stickyans) { &sticky_load; }
  }


#  option/arg processing
#
if ($#ARGV < 1) { &usage; }

$dir = $ARGV[0]; shift;
@files = @ARGV;

if (defined($ENV{"CVSLINES_REM"}))
  { $Remplan = 1; }
else
  { $Remplan = 0; }

$rev = "$Revision: 1.2 $_";
$rev =~ s/: //; $rev =~ s/ .*//;

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
   = localtime(time);
$mon++;

$cmds = sprintf("# $Mynamebase $rev $here %d/%d/%d %02d:%02d:%02d\n",
                  $mon, $mday, $year, $hour, $min, $sec);

$some_but_not_all = <<LIT;
In this case, the line(s) that should not get the revision should
probably be moved onto a branch before applying this revision.
 
This is a significant operation, which must be applied to all of the
files in the module. Please consult with Release Engineering before
proceeding with these changes.
LIT

$some_but_not_all_msg = <<LIT;

$Mynamebase: problem:
 
You have indicated that you want some, but not all, of the
non-branched lines on branch "%s" to get this revision.
 
$some_but_not_all
LIT


$some_but_not_all_spec_msg = <<LIT;

$Mynamebase: problem:

You have indicated that you want some, but not all, of the
lines currently specified by "%s" to get this revision.

$some_but_not_all
LIT

foreach $file (@files)
  {
    #  Reset all those pesky globals here!
    #
    undef %state;
    undef %CVS_Spoofed;
    undef %Lines_On_Rev;
    undef $RCS_Valid;
    undef %RCS_Tags;
    undef %RCS_Branchtags;
    undef %RCS_Revs;
    undef $RCS_Branch;
    undef %RCS_Texts;

    $filecmds = "\npath $here/$file\n";

    &set_Ent($file);

    if ($Verbose || (! $Batsel))
      {
        print TTYO "\n$Mynamebase: making commit plan for:\n".
          "      file: $here/$file\n  cvs info: $Ent_Rev/$Ent_Time/$Ent_Opts/$Ent_Tag\n";
      }

    &log("$here/$file: $Ent_Rev/$Ent_Time/$Ent_Opts/$Ent_Tag");

    $this_line = "?";

    foreach $line_name (@CVS_Lines)
      {

        ($spec, $state, $specrev) = &setspecs($line_name);
        if ($spec ne $Ent_Tag) { next; }

        #  If > 1 lines match, we need resolution...
        #
        if ($this_line ne "?")
          { $this_line = &this_line_resolve(); last; }

        $this_line = $line_name;
      }
    if ($this_line eq "?")
      { &noteNbail("problem: couldn't determine this_line for \"$file\"."); }

    $this_lgroup = $CVS_Lines_Lgroups{$this_line};

    #  set up the RCS revs information for this file...
    #
    if (&set_RCS_revs($CVS_Repository, $file, 0, 1)
          && (! &set_RCS_revs($CVS_Repository, $file)))
      { &noteNbail("couldn't set RCS revision information for \"$here/$file\""); }

    if (1)
      { 
        #  I had put this in to spoof CVS away from creating a branch
        #  off of the vendor release branch when adding a file to a
        #  local branch, in the spirit of "minimize branching".
        #
        #  Is it time to play CVS spooferino?
        #

        #  Note: we don't do lgroups-exclusion here, since applying
        #  the spoof should be orthogonal to anything else we do
        #  below...

        foreach $line_name (@CVS_Lines)
          {
            ($spec, $state, $specrev) = &setspecs($line_name);
            $currev = &rev_on_line($spec);

            if (
    	     ($specrev =~ /\.0\.([0-9]+)$/) 
                 && (! defined($RCS_Revs{&branch_t($specrev).".1"}))
                 && ($RCS_Texts{$currev} eq "")
                 && (&branch($currev) eq $RCS_Branch)
               )
              {
                #  Pretend that $specrev is an undiverged branch on the
                #  root point of the default branch
                #
                $nextry = 2;
    
                ($br = $RCS_Branch) =~ s/.[0-9]+$//;
    
                while (defined($RCS_Branchtags{"$br.0.$nextry"}))
                  { $nextry += 2; }

                $RCS_Tags{$spec} = "$br.0.$nextry";
                $CVS_Spoofed{$line_name} = 1;
              }
          }
      }    


    if ($Ent_Rev eq "0")
      {      
	#  This file has been scheduled for addition...
        #
        $this_rev = "new";
        $this_branch = "new";

        #  What the new head rev would be; "M.n", where "M" is the
        #  same as the highest major revision number of any other file
        #  known to CVS_Entries. (And "n" is the first available minor
        #  number) At least, that's what the cvs source appears to do!
        #
        undef $max_rev; undef $E_rev; undef $E_name;
        $max_rev = 1;
        foreach $Entry (@CVS_Entries)
          {
            ($dummy, $E_name, $E_rev) = split(/\//, $Entry);
            $E_rev =~ s/\..*//;
            if ($E_rev > $max_rev) { $max_rev = $E_rev; }
          }
        $n_try = 1;
        while (1)
          {
            $n_try_rev = "$max_rev.$n_try";
            if (! defined($RCS_Revs{$n_try_rev})) { last; }
            $n_hi_rev = $n_try_rev;
            $n_try++;
          }
        $this_newrev = $n_try_rev;

        #  What the new branch rev would be. Wholly new files will
        #  commit to "1.1.{2,4,6,...}.1" branched from a stub 1.1
        #  (appears to be what cvs does, based on experimentation).
        #
        #  If the file already exists, new branches will be started at
        #  the first available branch point off of the current head.
        #  
        if (! $RCS_Valid)
          {
            #  this is the first ever commit for this file, any line
            #
            $this_newbranch = "1.1.2.1";
          }
        else
          {
            #  If we get here, then we know we have the RCS revs info
            #  from above...
            #
            $n_try = 2;
            while (1)
              {
                $n_try_rev = "$n_hi_rev.$n_try.1";
                if (! defined($RCS_Revs{$n_try_rev})
                     && ! defined($RCS_Branchtags{"$n_hi_rev.0.$n_try"}))
                  { last; }
                $n_try += 2;
              }
            $this_newbranch = $n_try_rev;
          }

        if ($CVS_Tag eq "head")
          { $this_on_branch = 0; } else { $this_on_branch = 1; }
      }
    else
      {
        #  OK, get some general information about the rev we're working on
        #  in the working tree... $this_* variables refer to this one.
        #
        ($spec, $state, $specrev) = &setspecs($this_line);

        if (&is_branch($specrev))
          { $this_on_branch = 1; } else { $this_on_branch = 0; }

        $this_rev = &rev_on_line($spec);

        if ($this_rev !~ /^(.+)\.([0-9]+)$/)
          { &noteNbail("problem: internal error: malformed \$specrev \"$specrev\"."); }
        $this_branch = &branch($this_rev);
      }

    #  when we get here, we have...
    #
    #    $this_line
    #    $this_rev
    #    $this_branch
    #    $this_lgroup
    #
    #  if $this_rev = "new", we also have...
    #
    #    $this_newrev
    #    $this_newbranch
    #    $this_on_branch
    #

    #  Summary headings...
    #

    if ($Verbose)
      {
        printf TTYO "\n";
        printf TTYO "   $Fmt\n", "line", "spec", " ", "spec rev", "cur rev";
        printf TTYO "   $Fmt\n", $D_Line, $D_Spec, " ", $D_Specrev, $D_Currev;
      }

    $stsmsg = "";

    #  Keep track of whether all lines sharing a given specrev want
    #  the change (or not).
    #
    undef %linespec_wants;
    undef %linespec_nowants;

    foreach $line_name (@CVS_Lines)
      {
        ($spec, $state, $specrev) = &setspecs($line_name);

        if ($line_name eq $this_line) { $linespec_wants{$spec} = 1; }

        $currev = &rev_on_line($spec);

        if ($this_rev eq "new")
          {
            if ((! $Nolgroups) && $CVS_Lines_Lgroups{$line_name} ne $this_lgroup)
              {
                $state{$line_name} = "n";
                if (! $Showall) { $linespec_nowants{$spec} = 1; next; }
                $stsmsg .= "xx ";
              }
            elsif ($line_name eq $this_line)
              {
                $stsmsg .= ">> ";
                $state{$line_name} = "c";
                $curbranch &branch($this_newrev);
              }
            elsif ($currev eq "none")
              {
                $stsmsg .= "a? ";
                $state{$line_name} = "a?";
                $curbranch &branch($this_newbranch);
              }
            else
              {
                $stsmsg .= "   ";
                $state{$line_name} = "n";
              }
          
            $stsmsg .= sprintf("$Fmt\n", $line_name, $spec, " ", "(none)", "(none)");
            next;
          }

        $curbranch = &branch($currev);

        if ((! $Nolgroups) && $CVS_Lines_Lgroups{$line_name} ne $this_lgroup)
          {
            $state{$line_name} = "n";
            if (! $Showall) { $linespec_nowants{$spec} = 1; next; }
            $stsmsg .= "xx ";
          }
        elsif ($line_name eq $this_line)
          {
            $stsmsg .= ">> ";
            $state{$line_name} = "c";
          }
        elsif ($currev eq $this_rev)
          {
            $stsmsg .= "u? ";
            $state{$line_name} = "u?";
          }
        elsif ($specrev ne "none")
          {
            $stsmsg .= "m? ";
            $state{$line_name} = "m?";
          }
        else
          {
            $stsmsg .= "   ";
            $state{$line_name} = "n";
          }
    
        if ($CVS_Spoofed{$line_name}) { $spoofed = "*"; } else { $spoofed = " "; }
        $stsmsg .= sprintf("$Fmt\n", $line_name, $spec, $spoofed, $specrev, $currev);
        if (defined($Lines_On_Rev{$currev}))
          { $Lines_On_Rev{$currev} .= " $line_name"; } else { $Lines_On_Rev{$currev} = $line_name; }
      }

    if ($Verbose) { print TTYO $stsmsg; &log($stsmsg); }

    #  OK, see if they want these changes to go into any other lines...
    #
    $headed = 0;

    $selmsg = "user-selections:\n";
    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} =~ /^[aum]\?/)
          {
            if (! $Batsel)
              {
                if ($Stickyans && defined($Stickyans{$line_name}))
                  { $ans = $Stickyans{$line_name}; }
                else
                  {
                    if (! $headed)
                      {
     		        if ($this_rev eq "new")
                          { print TTYO "\nShould this file be included in...\n"; }
                        else
                          { print TTYO "\nShould these changes also be applied to...\n"; }
                        $headed = 1;
                      }
                    
                    $def = "n";

                    # Check for option defaulting this to "y"...
                    #
                    @opts = split(/,/, $CVS_Lines_Opts{$this_line});
                    if (grep(/^\+$line_name$/, @opts)) { $def = "y"; }
                    $ans = &ask(sprintf("   %-10s", $line_name), $def, "y", "n");
                  }
              }
            else
              { $ans = $Select_ans{$line_name}; }

            $selmsg .= "$line_name?$ans\n";
            ($spec, $state, $specrev) = &setspecs($line_name);

            if ($ans eq "y")
              {
                $state{$line_name} =~ s/\?//;
                $linespec_wants{$spec} = 1;
                if ($Stickyans) { $Stickyans{$line_name} = "y"; }
              }
            else
              {
                $state{$line_name} = "n";
                $linespec_nowants{$spec} = 1;
                if ($Stickyans) { $Stickyans{$line_name} = "n"; }
              }
          }
      }
    &log($selmsg);

    if ($Stickyans) { &sticky_store; }

    #  Look for cases where not all lines sharing a specrec want the
    #  change...
    #
    foreach $line_name (@CVS_Lines)
       {
        ($spec, $state, $specrev) = &setspecs($line_name);
        if ($linespec_wants{$spec} && $linespec_nowants{$spec})
          {
            printf TTYO $some_but_not_all_spec_msg, $spec;
	    &log("needs branching");
            exit 1;
          }
      }

    if ($this_rev eq "new")
      { 
        undef $newrev;

        #  First, make sure that if some on the head want it,
        #  all do...
        #
        &assert_keys;
        foreach $line_name (@CVS_Lines)
          {
            if ($state{$line_name} eq "n") { next; }
            ($spec, $state, $specrev) = &setspecs($line_name);

            if ($spec eq "head")
              { $newrev = $this_newrev; }
            else
              { if (! defined($newrev)) { $newrev = $this_newbranch; } }
          }      

        &assert_keys;
        foreach $line_name (@CVS_Lines)
          {
            if ($state{$line_name} eq "n") { next; }
            ($spec, $state, $specrev) = &setspecs($line_name);

            if ($newrev eq $this_newrev)
              {
                if ($spec eq "head")
                  {
		    if ($line_name eq $this_line)
                      { $state{$line_name} = "c:$newrev"; }
                    else
                      { $state{$line_name} = "i:$newrev"; }
                  }
                else
                  { $state{$line_name} = "a:$newrev"; }
              }
            else
              {
                if ($line_name eq $this_line)
                  { $state{$line_name} = "c:$newrev"; }
                else
                  { $state{$line_name} = "a:$newrev"; }
              }               
          }      
              
        goto plan_for_new;
      }

#&dump_state();

    #  Now construct the set of unique RCS revisions that want this
    #  mod, in $want_revs...

    undef %want_revs;

    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} eq "n") { next; }
        ($spec, $state, $specrev) = &setspecs($line_name);

        if ($this_rev eq "new")
          {
            if ($spec eq "head")
              { $currev = $this_newrev; }
            else
              { $currev = $this_newbranch; }
          }
        else
          { $currev = &rev_on_line($spec); }
        if (! defined($want_revs{$currev}))
          { $want_revs{$currev} = $line_name; }
        else
          { $want_revs{$currev} .= " $line_name"; }
      }      
          
    #  OK, now we need to decide what actions to take for each line
    #  in each want_rev.
    #
#&dump_state();

    foreach $rev (keys(%want_revs))
      {
#print TTYO "====== rev<$rev>\n";

       	# makes it easy to tell whether a given lines wants this rev
        #
        undef %wanters;

        undef $next_rev; undef $next_branch_rev;

        $wanter_lines = $want_revs{$rev};
        @wanter_lines = split(/ /, $wanter_lines);

        #  Build $wanters{}
        #
        foreach $wanter_line (@wanter_lines)
          { $wanters{$wanter_line} = 1; }

        #  Now, we want to know what the potential next and
        #  newly branched revs would would be for this rev...


        if ($this_rev eq "new")
          {
            $next_rev = $this_newrev;
            $next_branch_rev = $this_newbranch;
          }
        else
          {
            $next_rev = &nextrev($rev);

            undef $min_branch_n;

            #  Now, if any specs are undiverged branch revs, we need to 
            #  set next_branch_rev appropriately...
            #
            foreach $wanter_line (@wanter_lines)
              {
                ($spec, $state, $specrev) = &setspecs($wanter_line);
                if ($specrev =~ /^(.+)\.0\.([0-9]+)$/)
                  {
#print TTYO "WANTER LINE BRANCHTAG <$wanter_line><$specrev> rev<$rev>\n";
                    $n = $2;                    
      		    # ...it's a branch rev spec...
                    if ($rev !~ /^$1.$2.[0-9]+$/)
                      {
#print TTYO "NOT BRANCHED!\n";
                        # ...and it is not branched. (We know this,
                        # beacause the $rev we're looking at is not on
                        # the indicated branch!)
                        #
                        if (!defined($min_branch_n) || $n < $min_branch_n)
                          { $min_branch_n = $n; }
                      }
                  }
              }

            if (defined($min_branch_n))
              { $next_branch_rev = "$rev.$min_branch_n.1"; }
            else
              { } # ??? anything for $next_branch_rev?
          }
#print TTYO "NEXT_REV <$next_rev> NEXT_BRANCH_REV <$next_branch_rev>\n";

        #  Next, we look at which lines want what, to determine whether
        #  we're hosed (need to branch an unbranched line), we can
        #  check in to the $next_rev (and update any undiverged branch
        #  tags), or need to check in to a newly diverged branch (and
        #  update other branch tags if necessary).
        #

        #  This needs to see lines that are on this rev, but don't
        #  want it, hence this needs to be a separate loop from the
        #  above!
        #

        $nb_wanting = 0;      # number that are not undiverged branches that want it
        $nb_not_wanting = 0;  # number that are not undiverged branches that don't want it
        $b_wanting = 0;       # number that are undiverged branches that want it
        $b_not_wanting = 0;   # number that are undiverged branches that don't want it

        $lines = $Lines_On_Rev{$rev};  # Going to look at all lines on this rev

        @lines = split(/ /, $lines);
        foreach $line_name (@lines)
          {

#print TTYO "line_name <$line_name>\n";

            ($spec, $state, $specrev) = &setspecs($line_name);

            $b = 0;
	    #  Are we a "non-branched" line?
            #  
            if ($specrev =~ /^(.+)\.0\.([0-9]+)$/)
              {
	        #  We're a branch rev spec...
                if ($rev !~ /^$1.$2.[0-9]+$/)
                  { $b = 1; }
              }              

            if ($b)
              {
                if (defined($wanters{$line_name}))
                  { $b_wanting++; } else { $b_not_wanting++; }
              }
            elsif (defined($wanters{$line_name}))
              { $nb_wanting++; } else { $nb_not_wanting++; }

          }		
            
#print TTYO "\$#lines = <$#lines>\n";
#print TTYO "nb_wanting<$nb_wanting> nb_not_wanting<$nb_not_wanting>\n";
#print TTYO "b_wanting<$b_wanting> b_not_wanting<$b_not_wanting>\n";

        if ($nb_wanting > 0 && $nb_not_wanting > 0)
          {
            my $branch;
            ($branch = $rev) =~ s/.[0-9]+$//;
            printf TTYO $some_but_not_all_msg, $branch;
	    &log("needs branching");
            exit 1;
          }

        #  OK, we get here with enough information to know where the
        #  commit for this $rev should go...
        #
        if ($nb_wanting)
          { $new_rev = $next_rev; } else { $new_rev = $next_branch_rev; }

#print TTYO "nb_wanting<$nb_wanting> NEW REV <$new_rev>\n";

        #  OK, now figure out what to do with each line that wants it
        #
        foreach $wanter (@wanter_lines)
          {
            $state = $state{$wanter};

            ($spec, $devstate, $specrev) = &setspecs($wanter);
#print TTYO "WANTER <$wanter> state<$state> spec<$spec> spcrev<$specrev>\n";

            $b = 0;
	    #  Are we an undiverged  "branch tag" line?
            #  
            if ($specrev =~ /^(.+)\.0\.([0-9]+)$/)
              {
	        #  We're a branch rev spec...
                if ($rev !~ /^$1.$2.[0-9]+$/)
                  { $b = 1; }
              }              

            if ($state =~ /^[cu]/)
              {
                # Apparently, not required:

                # if ($b && ($nb_wanting + $nb_not_wanting) == 0)
                # if ($b && $nb_wanting)
                #   {
                #     $state{$wanter} = "T:$next_rev";
                #     next;
                #   }

                if (&nextrev($specrev) eq $new_rev)
                  # Then this line's action is a commit...
                  {
                    if ($state =~ /^c/)
                      { $state{$wanter} = "c:$new_rev"; }
                    else
                      { $state{$wanter} = "i:$new_rev"; }
                  }
                else
                  # Then this lines's action is a branch tag update
                  {
                    if ($state =~ /^c/ && $nb_wanting > 0 && $Verbose)
                      { &notify_re_tagswap($rev); }
                    $state{$wanter} = "t:$new_rev";
                  }
                next;
              }

            if ($state =~ /^m/)
              {
	        if (&nextrev($specrev) eq $new_rev)
                  { $state{$wanter} = "m:$new_rev"; }
                else
                  { $state{$wanter} = "t:$new_rev"; }
                next;
              }

            print TTYO "$Mynamebase: internal error: unexpected state \"$state\".";
            exit 1;
          }
      }

#print TTYO "\n 111\n";
#&dump_state();

# { . { . { . { . { . {


    #  OK, are any "i"'s really "c"'s?
    #
    #   First, note any "this commit" revision we have slated...
    #   (there can only be one!)
    #
    undef $this_commit_rev;
    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} =~ /^c:(.+)$/)
          {
            $this_commit_rev = $1;
            last;
          }
      }

    #   OK, now convert any "i:$this_commit_rev"s to "c"s...
    #     (or "t"s, if it's a spoofed line!)
    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} =~ /^i:(.+)$/)
          {
            if ($1 eq $this_commit_rev)
	      {
                if ($CVS_Spoofed{$line_name})
                  { $state{$line_name} = "t:$this_commit_rev"; }
                else
                  { $state{$line_name} = "c:$this_commit_rev"; }
              }
          }
      }

#print TTYO "\n 333\n";
#&dump_state();

    #    An element =~ "^c" will get this commit (branching if a natural consequence)
    #    An element =~ "^a" will be updated with a branch tag creation.
    #    An element =~ "^t" wants to be a tag update.
    #    An element =~ "^m" wants a merge 'n commit.
    #    An element =~ "^i" wants a commit.
    #    An element =~ "^n" wants no action.
    #    
    #  (but we still need to normalize to reflect the current check-in,
    #  and updates for multiple non-branched lines that are currently
    #  at the same revision).
    #

    plan_for_new:

    if ($Verbose || (! $Noconfirm))
      {
        print TTYO "\nAction plan:\n\n";
        &assert_keys;
        foreach $line_name (@CVS_Lines)
          {
            if ((! $Nolgroups) && ($CVS_Lines_Lgroups{$line_name} ne $this_lgroup
                  && (! $Showall)))
              { next; }

            $action = $state{$line_name};

            $msg = sprintf("   %-10s: ", $line_name);
            print TTYO $msg;

            ($spec, $state, $specrev) = &setspecs($line_name);
            if ($action =~ /^c/)
              { $msg = "update with this commit"; }
            elsif ($action =~ /^a/)
              { $msg = "update with a branch tag create"; }
            elsif ($action =~ /^t/)
              { $msg = "update with a branch tag update"; }
            elsif ($action =~ /^m/)
              { $msg = "update with a revision merge and commit"; }
            elsif ($action =~ /^i/)
              { $msg = "update with a commit"; }
            elsif ($action =~ /^n/)
              {
                $msg = "will not be updated";
                if ((! $Nolgroups) && $CVS_Lines_Lgroups{$line_name} ne $this_lgroup)
                  { $msg .= " (different lines group \"$CVS_Lines_Lgroups{$line_name}\")"; }
              }
            else
              { &noteNbail("problem: internal error: unresolved action: \"$action\"."); }

            print TTYO $msg;

            if ($action !~ /^n/)
              {
                $action =~ m/:(.+)$/; $r = $1;
                if ($CVS_Spoofed{$line_name})
                  { $msg = " [*$r]"; } else { $msg = " [$r]"; }
                print TTYO $msg;
              }
        
            $msg = "\n"; print TTYO $msg;
          }
      }

    if ($Noconfirm)
      { $ans = "y"; }
    else
      {
        $ans = &ask("\nProceed", "n", "y", "n");
        print TTYO "\n";
      }

    if ($ans eq "n")
      {
        print TTYO <<LIT;
$Mynamebase: no "$here/$g_planfile" file written;
$Myspacbase  no files will be committed from this directory.

LIT
        &log("not proceeding");
        exit 1;
      }

    #  OK, generate the cvs commands in a script for later execution.
    #
    undef %ci_revisions;  # Tracks revisions we've checked in.

    #  Note that we do all non-merge updates first, so we never have
    #  to worry about the working file being some other merged version
    #  when doing these update
    #  
    #  First, do any straight commits...
    #
    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} =~ /^c:(.+)$/)
          {
            ($spec, $state, $specrev) = &setspecs($line_name);
            if (! defined($ci_revisions{$1}))
              {
                if ($CVS_Spoofed{$line_name})
                  { $r = "$spec:$1"; } else { $r = "$1"; }
                $filecmds .= "commit this $r\n";
                $ci_revisions{$1} = 1;
              }
            #delete($state{$line_name});
            $state{$line_name} =~ s/^./X/;
          }
      }

    #  Now, any "not this" commits" (which may be the initial check-in,
    #  if we punted in favor of branch tagging...)
    #
    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} =~ /^i:(.+)$/)
          {
            ($spec, $state, $specrev) = &setspecs($line_name);
            if (! defined($ci_revisions{$1}))
              {
		if ($merge_diffs ne "")
                  { $filecmds .= "merge $spec\n"; }
                else
                  { $filecmds .= "commit $spec $1\n"; }
                $ci_revisions{$1} = 1;
              }
            #delete($state{$line_name});
            $state{$line_name} =~ s/^./X/;
          }
      }

#    #  Now, the first "commit plus branch tag update"s,
#    #  if there are any.
#    #
#    &assert_keys;
#    foreach $line_name (@CVS_Lines)
#      {
#        if ($state{$line_name} =~ /^T:(.+)$/)
#          {
#            ($spec, $state, $specrev) = &setspecs($line_name);
#            if (! defined($ci_revisions{$1}))
#              {
#                $filecmds .= "commitTag $spec $1\n";
#                $ci_revisions{$1} = 1;
#              }
#            #delete($state{$line_name});
#            $state{$line_name} =~ s/^./X/;
#          }
#      }

    #  Now, any plain 'ole merges
    #
    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} =~ /^m:(.+)$/)
          {
            ($spec, $state, $specrev) = &setspecs($line_name);
            if (! defined($ci_revisions{$1}))
              {
                $filecmds .= "merge $spec $1\n";
                $ci_revisions{$1} = 1;
              }
            #delete($state{$line_name});
            $state{$line_name} =~ s/^./X/;
          }
      }

    #  Now, any branch tag updates
    #  (any remaining "T"s are just simple updates at this point)
    #
    &assert_keys;
    foreach $line_name (@CVS_Lines)
      {
        if ($state{$line_name} =~ /^[atT]:(.+)$/)
          {
            ($spec, $state, $specrev) = &setspecs($line_name);
	    $filecmds .= "branchtag $spec $1\n";
            #delete($state{$line_name});
            $state{$line_name} =~ s/^./X/;
          }
      }

    &log($filecmds);
    $cmds .= $filecmds;

  }


if (! open(CMDS, ">$here/$Planfile"))
  { &noteNbail("problem: can't create \"$here/$Planfile\" to write"); }

print CMDS $cmds;
close CMDS;

#  OK, if we got here, then we wrote a valid $Planfile
#  spec - tell the user
#
print TTYO "$Mynamebase: wrote $here/$Planfile\n";

exit 0;
