#!/usr/bin/perl -w

# prcs2svn - Convert a PRCS project to a SVN repository
# Copyright (C) 2005  Rafael Laboissiere
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# $Id$

=head1 NAME

prcs2svn - Convert a PRCS project to a SVN repository

=head1 SYNOPSIS

B<prcs2svn> I<prcs_project> I<svn_repository>

=head1 DESCRIPTION

prcs2svn is a simple minded program to convert a PRCS project to a SVN
repository.  Every version of the PRCS project being converted will be
accessed as a tagged version in the SVN repository.  This means that
after running prcs2svn, the following commands will checkout the same
set of files:

  prcs checkout -r <revision> <prcs_project>
  svn checkout <svn_repository>/tags/<revision> .

The I<svn_repository> must already exist in the SVN server and the
access rights must be already given to the user running the prcs2svn
script, otherwise it will fail.  A typical usage would be:

  [as root]
  rm -rf /svn/repo/<prcs_project>
  mkdir -p /svn/repo/<prcs_project>
  chown -R <user>:<group> /svn/repo/<prcs_project>

  [as user]
  prcs2svn <prcs_project> file:///svn/repo/<prcs_project>
  [or]
  prcs2svn <prcs_project> svn+ssh://<user>@<host>/svn/repo/<prcs_project>

prcs2svn completely ignores the merging tree of the PRCS project.  It
follows a simple algorithm to do the conversion: first, the list of
versions is obtained with the prcs info command.  Second, this list is
processed sequentially, taking account of file additions and
deletions.  This algorithm is okay inside a branch when there are no
merges and wheneach version has one parent and one child.  However,
the algorithm is suboptimal when changing branches in the list of
versions.

Another limitation of prcs2svn is the fact that file and directory
renaming is completely ignored.  Renamed items will appear as deleted
and added when going from one tagged SVN revision to the next.

=head1 OPTIONS

=over 4

=item B<-h>

Show command synopsis.

=item B<-q>

Do not show the output of the prcs and svn commands being called.

=back

=head1 AUTHOR

Rafael Laboissiere

=head1 SEE ALSO

prcs (1), svn (1)

=cut


use Getopt::Long;

# Get name of this program, striping the path

(my $prog = $0) =~ s:.*/::;

# Print command synopsis, either to stderr (if arg == 1) or to stdout
# (if arg == 0)

sub usage {
  my $ret = shift;
  my $out = ($ret ? \*STDERR : \*STDOUT);
  print $out "Usage: $prog prcs_project svn_repository_path\n";
  exit $ret;
}

my ($help, $quiet, $tmpdir);

# Remove temporary directory used for the checkouts

sub cleanup {
  system "rm -rf $tmpdir"
    if defined $tmpdir;
}

# Run a system command, die if it fail

sub run {
  my $comm = shift;
  my $out = "";
  $out .= " > /dev/null" . (($comm =~ /^prcs /) ? " 2>&1" : "")
    if defined $quiet;
  if (system ("$comm $out") != 0) {
    cleanup ();
    print STDERR "$prog: failed to run \"$comm\"\n";
    die;
  }
}

# Process options

GetOptions ("h" => \$help, "q" => \$quiet);

defined $help
  and usage (0);

# Process input arguments

scalar @ARGV == 2
  or usage (1);

my $prcs_prj = $ARGV[0];
my $svn_repo = $ARGV[1];

# Get list of PRCS versions

my $info = `prcs info $prcs_prj`
  or die;

@versions = map {
  /$prcs_prj\s+([^\s]+)\s+/;
  $1;
} split ("\n", $info);

# Create temporary directory

$tmpdir = `tempfile`
  or die "Cannot create temporary directory";

chomp $tmpdir;
unlink $tmpdir;
mkdir $tmpdir;

chdir $tmpdir;

# Create SVN layout and import it

mkdir $prcs_prj;
mkdir "$prcs_prj/trunk";
mkdir "$prcs_prj/tags";

chdir $prcs_prj;
run "svn import . $svn_repo --message 'Initial repository layout'";
chdir "..";

# Loop over PRCS versions

for $ver (@versions) {

  # Checkout the SVN directories trunk and tags

  run "rm -rf $prcs_prj";
  run "svn checkout --non-recursive $svn_repo";
  chdir "$prcs_prj";
  run "svn checkout --non-recursive $svn_repo/tags";
  run "svn checkout $svn_repo/trunk";

  # Create tarball containing all the .svn directories

  $tarball = `tempfile`;
  chomp $tarball;
  run "tar cf $tarball `find trunk | grep /.svn`";
  run "rm -rf trunk";
  mkdir "trunk";

  # Checkout the PRCS specified version; remove the automatically
  # generated file *.prcs_aux

  chdir "trunk";
  run "prcs checkout -l -f -r $ver $prcs_prj";
  unlink ".$prcs_prj.prcs_aux";

  # Restore the .svn directories stored in the tarball, only if they
  # exist in the current PRCS version.

  chdir "..";
  run "tar xf $tarball "
    . join (" ", map {
        -d $_ ? $_ : "";
      } grep { m:/.svn$: } split ("\n", `tar tf $tarball`));
  unlink $tarball;

  # Check which items were added and deleted in relation to the last
  # SVN version

  chdir "trunk";
  map {
    if (/^\?\s+(.*)/) {
      run "svn add $1";
    } elsif (/^\!\s+(.*)/) {
      run "svn delete $1";
    } elsif (/^\~\s+(.*)/) {
      run "rm $1";
      run "svn delete $1";
      run "prcs checkout -l -f -r $ver $prcs_prj.prj $1";
      run "svn add $1";
    }
  } split ("\n", `svn status`);

  # Commit the SVN version using as log message the Version-Log of the
  # PRCS version

  my $log = `prcs info -l -r$ver $prcs_prj.prj`
    or die "Cannot run prcs info for version $ver";
  $log =~ s/\n/"@@@@@"/g;
  $log =~ /Version-Log:(.*)Project-Description:/m;
  ($log = $1) =~ s/"@@@@@"/\n/g;
  $log =~ s/^\s+//;
  $log = "Committed PRCS version $ver\n\n" . $log;

  my $logfile = `tempfile`;
  chomp $logfile;
  open (LOG, "> $logfile");
  print LOG $log;
  close LOG;
  run "svn commit --file $logfile";
  unlink $logfile;

  # Remove and checkout trunk, svn copying after to tags/<version>

  chdir "..";
  run "rm -rf trunk";
  run "svn checkout $svn_repo/trunk";
  run "svn copy trunk tags/$ver";
  run "svn commit tags/$ver --message 'Tagged PRCS version $ver'";

  chdir "..";

}

# Make sure the temporary directory is removed

cleanup ();
