# =============================
# pvcs2svn Tool
# =============================
# Author:  Thomas Wolkenstein (thomas@wolkenstein.com)
# Company: AMS Engineering STICHT GesmbH, A-4232 Hagenberg i. M.
# Version: 1.0
# Date:    24.09.2004
# =============================
# Description:
# -) This script copies a PVCS repository into an Subversion repository while keeping all pvcs revisions
#    including the correct check in date, author and comment. For every pvcs Versionlabel the script
#    creates a Subversin tag.
# -) directory tree:
#    - <your repos>
#      - branches
#      - tags
#      - trunk
#        - <pvcs project>
#        - <pvcs project>
#
# Notes:
# -) Use at your own risk.
# -) This script was designed for use with PVCS Version Manager, Version 6.8.10
# -) The script is dependant on following binaries: pcli, svn
# -) The Workpath setting for the files in PVCS is ignored, the workpath in the command line is used instead
# -) The Subversion Repository must be enabled to accept revision property changes.
#    Create an executable file named "pre-revprop-change" in the "hooks" directory
# -) Befor running the script you have tor create the target svn repository with "svnadmin create"
#    No further changes should be made. The script will create the /trunk /tags /brunches directories
# -) It doesn't matter where you run the script, but absolut pathes are recommended

use strict;


if($#ARGV != 6) {
  print "usage: perl pvcs2svn.pl <pvcsuser> <pvcsprojectpath> <pvcsproject> <svnuser> <svnpwd> <svnurl> <svnworkpath>\n\n";
  print "<pvcsuser>        The User of the PVCS Repository. SuperUser rights are needed.\n";
  print "                  Use syntax <user>:<pwd> to specify a password.\n";
  print "<pvcsprojectpath> The absolut path to the pvcs repository. (like in the GUI)\n";
  print "                  No backslash at the end.\n";
  print "<pvcsproject>     The PVCS Subproject. Use \"/\" to convert everything.\n";
  print "                  otherwise specify a subproject. Only leading slash.\n";
  print "<svnuser>         The Subversion user. Write access is mandatory.\n";
  print "<svnpwd>          The Password of the Subversion User.\n";
  print "<svnurl>          The Url to the Suversion repository.\n";
  print "<svnworkpath>     A lokal directory where a working copy is created.\n";
  print "                  The scipt will use this directory to perform all operations.\n";
  exit;
}

my $PVCSUser = $ARGV[0];
my $PVCSProjectPath = $ARGV[1];
my $PVCSProject = $ARGV[2];
my $SVNUser = $ARGV[3];
my $SVNPwd = $ARGV[4];
my $SVNURL = $ARGV[5];
my $SVNWorkPath = $ARGV[6];

my $PVCSvlog = "pvcs2svnvlog.txt";

# Clear File.
open(DATEI, ">$PVCSvlog");
close(DATEI);

# Generate the pvcs log file, which contains every revision
print "\n PVCS VersionLog wird generiert.\n";
system("pcli vlog -z -id$PVCSUser -f$PVCSvlog  -pr\"$PVCSProjectPath\" \"$PVCSProject\"");

# Read the pvcs log
open(DATEI, "<$PVCSvlog") || die "Die Datei $PVCSvlog wurde nicht gefunden oder konnte nicht zum Lesen geöffnet werden.";
my @PVCSvlogZeilen = <DATEI>;
close(DATEI);

print "\n$#PVCSvlogZeilen Lines read \n\n";

# Parsing PVCS Log
my @PVCSCheckIns;
my @Verzeichnisse;
my @VersionLabels;

my $Zustand = 0;
my @Erwartet;
push (@Erwartet,"^Archive:.+");
push (@Erwartet,"^Workfile:");
push (@Erwartet,"^Version labels:");
push (@Erwartet,"^\\s\\s\\s\"");
push (@Erwartet,"Description:");
push (@Erwartet,"");
push (@Erwartet,"^-----------------------------------");
push (@Erwartet,"^Rev");
push (@Erwartet,"^Checked in:");
push (@Erwartet,"^Author id:");
push (@Erwartet,"");
push (@Erwartet,"^===================================");

my $AktuellesVerzeichnis;
my $AktuelleDatei;
my @AktuelleVersionLabels;
my $AktVerLabIndex=0;
my $AktuelleDescription;
my @AktuelleRevisions;
my $AktRevIndex=0;
my $AktuelleRevisionNummer;
my $AktuelleRevisionDatum;
my $AktuelleRevisionAuthor;
my $AktuelleRevisionKommentar = " ";

my $Monat;


foreach(@PVCSvlogZeilen) {
  #print "\n$_";
  if($Zustand == 0) {
    #print "Erwartet: $Erwartet[0]\n";
    if( $_ =~ /$Erwartet[0]/) {
      $_ =~ /\\archives\\(.*)\\([\w-\.]+)/;
      $AktuellesVerzeichnis = $1; # Verzeichnis bestimmen
      #print "Verzeichnis: $1\n";
      my $i;
      my $VIndex = -1;
      for($i = 0; $i <= $#Verzeichnisse; $i++) {
        if($1 eq $Verzeichnisse[$i]) {
          $VIndex = $i;
        }
      }
      if($VIndex == -1) {
        push(@Verzeichnisse,$1);
        $VIndex = $#Verzeichnisse;
      }
      $Zustand = 1;
      #print "Zustand: 1\n";
    }
  } elsif($Zustand == 1) {
    if($_ =~ /$Erwartet[1]/) {
      $_ =~ /$Erwartet[1]\s*(\w*\.\w*)/; # Dateiname bestimmen
      $AktuelleDatei = $1;
      #print "Datei: $1\n";
      $Zustand = 2;
      #print "Zustand: 2\n";
    }
  } elsif($Zustand == 2) {
    if($_ =~ /$Erwartet[2]/) {
      $Zustand = 3;
      #print "Zustand: 3\n";
    }
  } elsif($Zustand == 3) {
    if($_ =~ /$Erwartet[3]/) {              # Version labels sammeln
      $_ =~ /"(.*)" = (.*)/;
      #print "Version Labels: $1 = $2\n";
      $AktuelleVersionLabels[$AktVerLabIndex][0] = $1; #Label
      $AktuelleVersionLabels[$AktVerLabIndex][1] = $2; #Revision
      $AktVerLabIndex += 1;
      #print "Zustand: 3\n";
    }
    if($_ =~ /$Erwartet[4]/) {
      $Zustand = 5;
      #print "Zustand: 5\n";
    }
  } elsif($Zustand == 5) {
    if($_ =~ /$Erwartet[6]/) {
      $Zustand = 7;
      #print "Zustand: 7\n";
    } elsif ($_ =~ /$Erwartet[5]/) {
      $AktuelleDescription .= $_;         # Description bestimmen
      #print "Zustand: 5\n";
    }    
  } elsif($Zustand == 7) {
    if($_ =~ /$Erwartet[7]/) {
      $_ =~ /^Rev\s(.*)/;
      $AktuelleRevisionNummer = $1;       # Revisionnummer
      $Zustand = 8;
      #print "Zustand: 8\n";
    }
  } elsif($Zustand == 8) {
    if($_ =~ /$Erwartet[8]/) {
      $_ =~ /^Checked in:\s\s\s\s\s(\w\w\w)\s(\d\d)\s(\d\d\d\d)\s(\d\d):(\d\d):(\d\d)/;
      if($1 eq "Jan") {
        $Monat = "01";
      }
      if($1 eq "Feb") {
        $Monat = "02";
      }
      if($1 eq "Mar") {
        $Monat = "03";
      }
      if($1 eq "Apr") {
        $Monat = "04";
      }
      if($1 eq "May") {
        $Monat = "05";
      }
      if($1 eq "Jun") {
        $Monat = "06";
      }
      if($1 eq "Jul") {
        $Monat = "07";
      }
      if($1 eq "Aug") {
        $Monat = "08";
      }
      if($1 eq "Sep") {
        $Monat = "09";
      }
      if($1 eq "Oct") {
        $Monat = "10";
      }
      if($1 eq "Nov") {
        $Monat = "11";
      }
      if($1 eq "Dec") {
        $Monat = "12";
      }
      $AktuelleRevisionDatum = "$3-$Monat-$2T$4:$5:$6.0Z"; # RevisionsDatum
      $Zustand = 9;
      #print "Zustand: 9\n";
    }
  } elsif($Zustand == 9) {
    if($_ =~ /$Erwartet[9]/) {
      $_ =~ /^Author id: (\w*)/;
      $AktuelleRevisionAuthor = $1;
      $Zustand = 10;
      #print "Zustand: 10\n";
    }
  } elsif($Zustand == 10) {
    if($_ =~ /$Erwartet[6]/) {
      $AktuelleRevisions[$AktRevIndex][0] = $AktuelleRevisionNummer;
      $AktuelleRevisions[$AktRevIndex][1] = $AktuelleRevisionDatum;
      $AktuelleRevisions[$AktRevIndex][2] = $AktuelleRevisionAuthor;
      $AktuelleRevisions[$AktRevIndex][3] = $AktuelleRevisionKommentar;
      $AktRevIndex += 1;
      $AktuelleRevisionNummer = "";
      $AktuelleRevisionDatum = "";
      $AktuelleRevisionAuthor= "";
      $AktuelleRevisionKommentar= "";
      $Zustand = 7;
      #print "Zustand: 7\n";
    } elsif($_ =~ /$Erwartet[11]/) {
      $AktuelleRevisions[$AktRevIndex][0] = $AktuelleRevisionNummer;
      $AktuelleRevisions[$AktRevIndex][1] = $AktuelleRevisionDatum;
      $AktuelleRevisions[$AktRevIndex][2] = $AktuelleRevisionAuthor;
      $AktuelleRevisions[$AktRevIndex][3] = $AktuelleRevisionKommentar;
      $AktuelleRevisionNummer = "";
      $AktuelleRevisionDatum = "";
      $AktuelleRevisionAuthor= "";
      $AktuelleRevisionKommentar= "";
      for(my $i = 0; $i <= $#AktuelleVersionLabels; $i++) {
        #print "Im VersionLabel die Revision durch das Datum ersetzen\n";
        #print "Revisionen: $#AktuelleRevisions\n";
        for(my $j = 0; $j <= $#AktuelleRevisions; $j++) {
         #print "$AktuelleVersionLabels[$i][0] = $AktuelleVersionLabels[$i][1]\n";
         if($AktuelleRevisions[$j][0] eq $AktuelleVersionLabels[$i][1]) {
            $AktuelleVersionLabels[$i][1] = $AktuelleRevisions[$j][1];
          }
        }
        
        my $LIndex = -1;
        #print "Ueberpruefen, ob das Versionlabel schon existiert.\n";
        for(my $j = 0; $j <= $#VersionLabels; $j++) {
          if($AktuelleVersionLabels[$i][0] eq $VersionLabels[$j][0]) {
            $LIndex = $j;
          }
        }
        #print "Neues VersionLabel wird hinzugefügt.\n";
        if($LIndex == -1) {
          $LIndex = $#VersionLabels + 1;
          $VersionLabels[$LIndex][0] = $AktuelleVersionLabels[$i][0];
          $VersionLabels[$LIndex][1] = $AktuelleVersionLabels[$i][1];
        }
        #print "Datum des VersionLabels aktualisieren.\n";
        if($VersionLabels[$LIndex][1] lt $AktuelleVersionLabels[$i][1]) {
          $VersionLabels[$LIndex][1] = $AktuelleVersionLabels[$i][1];
        }
      }
      for(my $i = 0; $i <= $#AktuelleRevisions; $i++) {
        my $j = $#PVCSCheckIns + 1;
        $PVCSCheckIns[$j][0] = $AktuelleRevisions[$i][0];
        $PVCSCheckIns[$j][1] = $AktuelleRevisions[$i][1];
        $PVCSCheckIns[$j][2] = $AktuelleRevisions[$i][2];
        $PVCSCheckIns[$j][3] = $AktuelleRevisions[$i][3];
        if(!($PVCSCheckIns[$j][3] =~ /\w/)) {
          $PVCSCheckIns[$j][3] = "Kein Kommentar";
        }
        $PVCSCheckIns[$j][4] = $AktuelleDatei;
        $PVCSCheckIns[$j][5] = $AktuellesVerzeichnis;
        $PVCSCheckIns[$j][6] = $AktuelleDescription;
        #print "$PVCSCheckIns[$j][0],$PVCSCheckIns[$j][1],$PVCSCheckIns[$j][2],$PVCSCheckIns[$j][3],$PVCSCheckIns[$j][4],$PVCSCheckIns[$j][5],$PVCSCheckIns[$j][6]\n";
      }
      $AktuelleDatei = "";
      $AktuellesVerzeichnis = "";
      $AktuelleDescription = "";
      @AktuelleVersionLabels = ();
      @AktuelleRevisions = ();
      $AktVerLabIndex = 0;
      $AktRevIndex = 0;
      $Zustand = 0;
      #print "Zustand: 0\n";
    } elsif($_ =~ /$Erwartet[10]/) {
      $AktuelleRevisionKommentar .= $_;
      $Zustand = 10;
      #print "Zustand: 10\n";
    }
  }
}

print "PVCS Repository\n";
print "Total Revisions: $#PVCSCheckIns\n";
print "VersionLabels  : $#VersionLabels\n\n";

my @SortierteVersionLabels = sort(VersionLabelSort @VersionLabels);
my @SortiertePVCSCheckIns = sort (PVCSCheckInSort @PVCSCheckIns);
my @SortierteVerzeichnisse= sort(@Verzeichnisse);

sub VersionLabelSort {
  if(@{$a}[1] lt @{$b}[1]) {
    return -1;
  } elsif (@{$a}[1] eq @{$b}[1]) {
    return 0;
  } else {
    return 1;
  }
}

sub PVCSCheckInSort {
  if(@{$a}[1] lt @{$b}[1]) {
    return -1;
  } elsif (@{$a}[1] eq @{$b}[1]) {
    return 0;
  } else {
    return 1;
  }
}


for(my $i = 0; $i <= $#SortierteVersionLabels ; $i++) {
  print "VersionLabel: $SortierteVersionLabels[$i][0] am $SortierteVersionLabels[$i][1]\n";
}

#for(my $i = 0; $i <= $#SortiertePVCSCheckIns ; $i++) {
#  print "Datei: $SortiertePVCSCheckIns[$i][4]\n";
#  print "Revision: $SortiertePVCSCheckIns[$i][0]\n";
#  print "Datum: $SortiertePVCSCheckIns[$i][1]\n";
#  print "Author: $SortiertePVCSCheckIns[$i][2]\n";
#  print "Kommentar: $SortiertePVCSCheckIns[$i][3]\n";
#  print "Verzeichnis: $SortiertePVCSCheckIns[$i][5]\n";
#  print "Description: $SortiertePVCSCheckIns[$i][6]\n";
#}

#for(my $i = 0; $i <= $#SortierteVerzeichnisse ; $i++) {
#  print "Verzeichnisse: $SortierteVerzeichnisse[$i]\n";
#}

my $LabelIndex = 0;
my $SVNprop = "pvcs2svnprop.txt";
my $SVNm = "pvcs2svnm.txt";
my $SVNDiffLog = "pvcs2svndifflog.txt";
my $SVNCommitLog = "pvcs2svncommitlog.txt";
my $PVCSGetLog = "pvcs2svngetlog.txt";

open(DATEI, ">$SVNCommitLog");
close(DATEI);
open(DATEI, ">$PVCSGetLog");
close(DATEI);


# Subversion Verzeichnisstruktur aufbauen:
system("mkdir $SVNWorkPath\\branches");
system("mkdir $SVNWorkPath\\tags");
system("mkdir $SVNWorkPath\\trunk");
for(my $i=0; $i <= $#SortierteVerzeichnisse ; $i++) {
  system("mkdir $SVNWorkPath\\trunk\\$SortierteVerzeichnisse[$i]");
}
# In Repository importieren
#print("svn import -m\"Verzeichnisstruktur\" $SVNWorkPath $SVNURL");
system("svn import --username $SVNUser --password $SVNPwd -m\"Verzeichnisstruktur\" $SVNWorkPath $SVNURL");
system("svn propset svn:date  --username $SVNUser --password $SVNPwd --revprop -r HEAD $SortiertePVCSCheckIns[0][1] $SVNURL/trunk");


# Lokale Verzeichnisstruktur löschen
system("rmdir /s /q $SVNWorkPath\\branches");   
system("rmdir /s /q $SVNWorkPath\\tags");   
#print("rmdir /s /q $SVNWorkPath\\trunk");
system("rmdir /s /q $SVNWorkPath\\trunk");

# Subversion Working Copy mit svn checkout initialisieren
#print("svn checkout $SVNURL/trunk $SVNWorkPath");   
system("svn checkout $SVNURL/trunk $SVNWorkPath");   

# Dateien aus PVCS holen und in SVN hinzufügen



my $SortiertePVCSCheckInsCount = $#SortiertePVCSCheckIns;
for(my $i=0; $i <= $SortiertePVCSCheckInsCount ; $i++) {
  my $Datei = $SortiertePVCSCheckIns[$i][4];
  my $Revision = $SortiertePVCSCheckIns[$i][0];
  my $Datum = $SortiertePVCSCheckIns[$i][1];
  my $Author = $SortiertePVCSCheckIns[$i][2];
  my $Kommentar = $SortiertePVCSCheckIns[$i][3];
  $Kommentar =~ s/å/ao/cgim;
  $Kommentar =~ s/ä/ae/cgim;
  $Kommentar =~ s/ö/oe/cgim;
  $Kommentar =~ s/ü/ue/cgim;
  $Kommentar =~ s/Å/Ao/cgim;
  $Kommentar =~ s/Ä/Ae/cgim;
  $Kommentar =~ s/Ö/Oe/cgim;
  $Kommentar =~ s/Ü/Ue/cgim;
  $Kommentar =~ s/ß/ss/cgim;
  my $Verzeichnis = $SortiertePVCSCheckIns[$i][5];
  my $Description = $SortiertePVCSCheckIns[$i][6];
  $Description =~ s/å/ao/cgim;
  $Description =~ s/ä/ae/cgim;
  $Description =~ s/ö/oe/cgim;
  $Description =~ s/ü/ue/cgim;
  $Description =~ s/Å/Ao/cgim;
  $Description =~ s/Ä/Ae/cgim;
  $Description =~ s/Ö/Oe/cgim;
  $Description =~ s/Ü/Ue/cgim;
  $Description =~ s/ß/ss/cgim;
  my $VerzeichnisSlashes = $Verzeichnis;
  $VerzeichnisSlashes =~ s/\\/\//cgim;
  print("pcli get -w$Revision -pr\"$PVCSProjectPath\" /$VerzeichnisSlashes/$Datei\n");
  system("pcli run -y -ns get -id$PVCSUser -o -a\"$SVNWorkPath\\$Verzeichnis\\$Datei\" -w$Revision -pr\"$PVCSProjectPath\" /$VerzeichnisSlashes/$Datei");
  if($Revision eq "1.0") {  #Falls die Datei zum ersten mal aus PVCS geholt wird, dann muss sie als Subversion Working File zur Working Copy hinzugefügt werden.
    #print("svn add \"$SVNWorkPath\\$Verzeichnis\\$Datei\"\n");
    system("svn add \"$SVNWorkPath\\$Verzeichnis\\$Datei\"");
    open(DATEI, ">$SVNprop") || die "Die Datei $SVNprop wurde nicht gefunden oder konnte nicht zum Schreiben geöffnet werden.";
    print DATEI $Description;
    close(DATEI);
    #print("svn propset description -F $SVNprop \"$SVNWorkPath\\$Verzeichnis\\$Datei\"\n");
    system("svn propset description -F $SVNprop \"$SVNWorkPath\\$Verzeichnis\\$Datei\"");
  }
  # Wenn die nächste PVCS Revision den gleichn Benutzer und das gleiche Kommentar trägt, dann wird nicht sofot in Subversion eingecheckt.
  if(($SortiertePVCSCheckIns[$i][2] ne $SortiertePVCSCheckIns[($i+1)][2]) || ($SortiertePVCSCheckIns[$i][3] ne $SortiertePVCSCheckIns[($i+1)][3])) {
    #system("svn diff --old $SVNURL/trunk --new $SVNWorkPath > $SVNDiffLog");
    #open(DATEI, "<$SVNDiffLog") || die "Die Datei $SVNDiffLog wurde nicht gefunden oder konnte nicht zum Lesen geöffnet werden.";
    #my @DiffLogZeilen = <DATEI>;
    #close(DATEI);
    #if($#DiffLogZeilen >= 4) {
      #print("svn add \"$SVNWorkPath\\$Verzeichnis\"\n");
      #system("svn add \"$SVNWorkPath\\$Verzeichnis\"");
      open(DATEI, ">$SVNm") || die "Die Datei $SVNm wurde nicht gefunden oder konnte nicht zum Schreiben geöffnet werden.";
      print DATEI $Kommentar;
      close(DATEI);
      #print("svn commit --username $SVNUser --password *** -F $SVNm \"$SVNWorkPath\"  > $SVNCommitLog\n"); # Commit durchführen
      system("svn commit --username $SVNUser --password $SVNPwd -F $SVNm \"$SVNWorkPath\" > $SVNCommitLog");
      # Den Author und das Datum der letzten Suversion Revision auf die Werte in PVCS ändern.
      open(DATEI, "<$SVNCommitLog") || die "Die Datei $SVNCommitLog wurde nicht gefunden oder konnte nicht zum Lesen geöffnet werden.";
      my @SVNCommitLogZeilen = <DATEI>;
      close(DATEI);
      print @SVNCommitLogZeilen;
      if($#SVNCommitLogZeilen > 1) {
        print("svn propset svn:author $Author\n");
        system("svn propset svn:author --username $SVNUser --password $SVNPwd --revprop -r HEAD $Author $SVNURL/trunk");
        print("svn propset svn:date $Datum\n");
        system("svn propset svn:date --username $SVNUser --password $SVNPwd --revprop -r HEAD $Datum $SVNURL/trunk");
      }
      if($SortierteVersionLabels[$LabelIndex][1] eq $Datum || $SortierteVersionLabels[$LabelIndex][1] lt $Datum) {
        print("svn copy --username $SVNUser --password *** $SVNURL/trunk $SVNURL/tags/$SortierteVersionLabels[$LabelIndex][0] -m \"Tagging $SortierteVersionLabels[$LabelIndex][0]\"\n");
        system("svn copy --username $SVNUser --password $SVNPwd $SVNURL/trunk $SVNURL/tags/$SortierteVersionLabels[$LabelIndex][0] -m \"Tagging $SortierteVersionLabels[$LabelIndex][0]\"");
        $LabelIndex += 1;
        print("svn propset svn:author $Author\n");
        system("svn propset svn:author --username $SVNUser --password $SVNPwd --revprop -r HEAD $Author $SVNURL/trunk");
        print("svn propset svn:date $Datum\n");
        system("svn propset svn:date --username $SVNUser --password $SVNPwd --revprop -r HEAD $Datum $SVNURL/trunk");
      }
      my $Fortschritt = ($i / $#SortiertePVCSCheckIns) * 100;
      print("-->Progress: $Fortschritt \%\n");
    #}
  }
}

print "\n\nDone.\n";