#!/usr/bin/perl -w # # PVCS to Perforce converter, phase III: construct Perforce depot # # Copyright 1997 Perforce Software. All rights reserved. # Written by James Strickland, July 1997 # Modified by Robert Cowham, 2000 - 2006. # Modified by Sven Erik Knop, 2009 onwards # # This script uses the metadata produced by earlier phases to direct a loop # which extracts PVCS revisions and performs the required Perforce commands # to construct a Perforce depot corresponding to the (improved) PVCS data. # require 5.0; # use strict; use integer; use lib '.'; use convert; use Change; use File::Path; use File::Spec; use Data::Dumper; # open all our input files my $msg="can't open for read"; open(FILES, "<$convert::metadata_dir/files") or die $msg; open(LABELS, "<$convert::metadata_dir/labels_details") or die $msg; open(LABELS_SUMMARY, "<$convert::metadata_dir/labels_summary") or die $msg; open(CHANGES, "<$convert::metadata_dir/changes") or die $msg; open(BRANCHES,"<$convert::metadata_dir/branches") or die $msg; open(MAPPING, ">$convert::metadata_dir/mapping.ns") or die "can't open for write: $!"; # variables to be initialized with metadata read from files my (%workfile, # maps archive -> workfile # e.g. "c:\foo.c__v" -> "foo.c" %file_type, # maps archive -> file type # e.g. "c:\foo.c__v" -> "ktext" %labels, # maps archive#revision -> list of labels # e.g. "c:\foo.c_v#1.1" -> "itworks","bobsyouruncle" %branches, # maps archive#revision -> list of branches # e.g. "c:\foo.c_v#1.1" -> "int#1.1.1","testing#1.1.2" %branch, # maps archive#branch_rev -> branch name # e.g. "c:\foo.c_v#1.1.2" -> "testing" %added, # maps depot file -> true/false # e.g. "//depot/foo.c" -> 1 ); # Check for P4 module installed my $USEP4 = 0; my $p4; eval{require P4; import P4;}; if (!$@) { $USEP4 = 1; print "Using P4Perl module\n"; $p4 = new P4; $p4->SetPort($convert::p4port); $p4->SetClient($convert::p4client); $p4->SetUser($convert::p4user); # $p4->SetPassword( "som value" ); # if password's required $p4->Connect() or die( "Failed to connect to Perforce Server: $convert::p4port $!\n" ); } # initialize %workfile and %file_type while(<FILES>) { chomp; my ($archive,$work,$file_type) = split(/#/,$_); $workfile{$archive}=$work; $file_type{$archive}=$file_type; } close(FILES); # initialize %labels while(<LABELS>) { chomp; my ($label,$archive,$revision) = split(/#/,$_); # Prefix label if specified $label = $convert::label_prefix . $label; my $index = join('#',$archive,$revision); push @{$labels{$index}}, $label } close(LABELS); # initialize %label and run p4 label once for each label which is not a # "delete" label my %labels_list; while(<LABELS_SUMMARY>) { chomp; my ($label) = $_; # Prefix label if specified $label = $convert::label_prefix . $label; next if($labels_list{$label}); if ($convert::delete_label_regex) { next if ($label =~ /^$convert::label_prefix$convert::delete_label_regex/); } # Update the Description field in the label. # Create a more specific view for the label - not just all depots which is the default. my $comment = "pvcstop4 label"; # Set default comment. my $label_view = "//$convert::depot/$convert::depot_root/..."; if (!$USEP4) { my $form=convert::p4run(" label -o $label"); # label names cannot contain spaces $form =~ s@(\nDescription:\s*)\n\s+\S[^\n]*\n@$1\n\t$comment@s; $form =~ s@(\nView:\s*)\n\s+.*\n$@$1\n\t$label_view@s; convert::p4run(" label -i",$form); } else { my ($form, @view, @result); $form = $p4->FetchLabel($label); $form->{'Description'} = $comment; push @view, $label_view; $form->{'View'} = \@view; @result = $p4->SaveLabel($form); convert::log("Label update:\n".join("\n", @result)); print_p4errors($p4, "Updating label $label"); } $labels_list{$label}=1; unlink "$convert::metadata_dir/labels/$label"; } close(LABELS_SUMMARY); # initialize %branches # NOTE: PVCS (and RCS) do not allow revisions which are branch points to # be deleted, so using a branch point as a trigger for creating branch copies # is guaranteed to work. while(<BRANCHES>) { chomp; my ($archive,$revision,$remainder) = split(/#/,$_,3); $branches{join('#',$archive,$revision)} = $remainder; } close(BRANCHES); # print timestamp print "Depot creation started " . scalar(localtime()) . "\n"; # OK, now we do the actual work. # For each change, there may or may not have to be # - revisions retrieved from PVCS using 'get' # - files marked as added or edited with 'p4 add' or 'p4 edit', # followed by 'p4 submit' # - files marked as being branched with 'p4 integrate' followed # by 'p4 submit' # - files marked as deleted with 'p4 delete' followed by 'p4 submit' # - labels updated with 'p4 labelsync' # # Read below for the details.. my ($c,$op); while( $c = get Change(\*CHANGES) ) { my ($change_number,@checkins,@delete_ops,@label_ops); # PVCS operations are written to a file and one get operation is performed # for performance reasons *and* to avoid overflowing the 127 character # limit on DOS command lines. (We avoid that with the p4 ops because we # keep them one per line with no repeated filenames). open(PVCS_FILELIST,">filelist") or die "can't open filelist: $!"; my $index; foreach $index (@{$c->changelist}) { my ($archive,$revision) = split(/#/,$index); my ($our_branch, $parent) = branch($archive,$revision); my $client_rel_file = convert::rel_dir( $workfile{$archive},$our_branch); $revision =~ /\.(\d+)$/; my $last_revision = $1; # needed to ensure the directory exists to hold the workfile # [SEK] This is now in the correct branch location. my $client_file = $convert::client_root . "/" . $client_rel_file; # needed to tell PVCS where to put the workfile my ($vol, $dirs, $file) = File::Spec->splitpath( $client_file ); my $client_dir = File::Spec->catpath($vol, $dirs, ""); # for p4 - could use $client_file, but I'd rather make sure we use # the same string that's written out to the mapping file my $depot_file = convert::join_paths( "//$convert::depot", $client_rel_file ); # create all needed directories on the path mkpath($client_dir); # check to see if this is a branch point # [SEK] need this to store the correct branch name to make branch() work my $branch_reference = ''; if( exists($branches{$index}) ) { # parse the list of branches my $remaining=$branches{$index}; while($remaining) { my ($branch_name,$branch_rev); ($branch_name,$branch_rev,$remaining) = split(/#/,$remaining,3); $branch_reference = join('#',$archive,$branch_rev); $branch{$branch_reference} = [$branch_name]; } } # schedule p4 add or edit operation # depending on whether file has already been added push(@checkins, [ $archive,$revision,$depot_file ] ); my $rev; if ( $parent and $last_revision == 0 ) { p4exec($depot_file, "integrate", $parent); p4exec($depot_file, "edit"); # this downgrades the integrate to an edit to allow changing of content $rev = 1; $added{$depot_file} = 1; } elsif ( $added{$depot_file}) { @result = p4exec($depot_file, "edit"); $rev = $result[0]->{'workRev'} + 1; } else { my @addcmd = ("add", "-t", "$file_type{$archive}"); p4exec($depot_file, @addcmd); $rev = 1; $added{$depot_file} = 1; } if ($branch_reference) { my $branch_source = "$depot_file#$rev"; push (@{$branch{$branch_reference}}, $branch_source); } # check if this revision is labelled if(exists($labels{$index})) { for (@{$labels{$index}}) { if($convert::delete_label_regex && $_ =~ /^$convert::label_prefix$convert::delete_label_regex/) { # marked for deletion $added{$depot_file}=0; push(@delete_ops, "$depot_file"); } else { open(LABEL, ">>$convert::metadata_dir/labels/$_") or die $msg; print LABEL "$depot_file#$rev\n"; close LABEL; } } } # add the file to the list of files for PVCS to get # and remove the file from the client (easier to do error checking this # way - if PVCS doesn't put a new file there, we'll bomb at the submit) unlink($client_file); # Check for env parameters to use to specify archive file seperators - required if files # contain things like "(" or ")" which are otherwise the default. if ($ENV{PVCS_LEFT_SEPARATOR} && $ENV{PVCS_RIGHT_SEPARATOR}) { print PVCS_FILELIST "-r$revision \"$archive" . $ENV{PVCS_LEFT_SEPARATOR} . $client_file . $ENV{PVCS_RIGHT_SEPARATOR} . "\"\n"; } else { print PVCS_FILELIST "-r$revision \"$archive($client_file)\"\n"; } } # extract all required revisions from PVCS # NOTE: PVCS ERROR OUTPUT IS BRAIN DEAD! # -q Quiet NoSignOn -xe stderr # 1 x x x nothing # x 1 x x nothing # 0 0 0 0 banner + 2 lines for each file extracted # 0 0 0 1 banner # 0 0 1 0 two lines for each file extracted # 0 0 1 1 banner # Observe that there is *no* way to get error output separately from normal # output (it all goes to stderr). If that weren't bad enough, there is # absolutely no way to redirect all stderr output without killing it. # Furthermore, there's no way I can stop someone from specifying Quiet # anyway (aside from following VCSCFG to the config file and editing it, # which is not a friendly thing to do). # # SO: we do error checking indirectly - if PVCS didn't put the file there, # the p4 submit will fail. close(PVCS_FILELIST); convert::run("get -q \@filelist"); # submit the change, and write out the association between PVCS # archive and revision number and Perforce file and change number $change_number = p4submit($c); # submit the change my $checkin; foreach $checkin (@checkins) { my ($a,$r,$depot_filename) = @$checkin; # write change number#filename to avoid confusion with filename#revision print MAPPING "$a#$r#$change_number#$depot_filename\n"; } foreach $op (@delete_ops) { p4exec($op, "delete"); } p4submit($c) if(scalar(@delete_ops)); } # now do the labelling print "\nLabelling started " . scalar(localtime()) . "\n"; for (keys(%labels_list)) { if (-f "$convert::metadata_dir/labels/$_") { # my @cmd = ("-x", "$convert::metadata_dir/labels/$_", "labelsync", "-l", $_); # my $result = p4exec($_, @cmd); convert::p4run(" -x $convert::metadata_dir/labels/$_ labelsync -l $_"); } } sub branch # return branch name for given archive and revision { return $convert::trunk_dir if($convert::ignore_branches); my ($archive,$revision) = @_; my $branch_number = $revision; $branch_number =~ s/\.[0-9]+$//; # chop dot and last number off return ($convert::trunk_dir, '') if($branch_number !~ /\./); # no dots -> trunk my $archive_and_branch_number = join('#',$archive,$branch_number); die "no branch name for revision $revision of $archive" if(!exists($branch{$archive_and_branch_number})); return @{$branch{$archive_and_branch_number}} ; } if ($USEP4) { $p4->Disconnect(); } # Run appropriate version of the command depending on if P4Perl is installed. sub p4exec { # Need to distinguish filename parameter as it might need quoting in case of spaces my $fname = shift; my @cmd = @_; my ($r, @result); if ($USEP4) { push @cmd, $fname; @result = $p4->Run(@cmd); log_p4errors($p4, "cmd:"); return @result if (!@result); convert::log( Dumper(@result) ); return @result; } else { return convert::p4run(join(" ", @cmd) . " \"$fname\"") } } # Run appropriate version of submit sub p4submit { my $c = shift; my ($change_number) = "UNSET"; if (!$USEP4) { $change_number = $c->submit; # submit the change } else { my $change_description = $c->change_description; my ($form,$output,@result); $form = $p4->FetchChange(); convert::log( Dumper($form) ); if ($form->{'Files'}) { $form->{'Description'} = $change_description; $result = $p4->RunSubmit($form)->[0]; print_p4errors($p4, "Submitting change"); $change_number = $result->{'change'}; # fix date, user on submitted change my $user = $c->author; my $date = $c->datetime; $form = $p4->FetchChange($change_number); $form->{'Date'} = $date; $form->{'User'} = $user; @result = $p4->SaveChange($form, "-f"); print_p4errors($p4, "Updating change"); print "Change $change_number submitted.\r"; # running total.. } else { my $dump = Dumper($form); print "WARNING: Change $dump empty.\r"; } } return $change_number; # returns the change number } sub print_p4errors { my ($p4, $msg) = @_; if ($p4->ErrorCount()) { print "**p4errors - $msg:\n"; foreach my $e ($p4->Errors()){ print $e . "\n"; } } } sub log_p4errors { my ($p4, $msg) = @_; if ($p4->ErrorCount()) { convert::log("**p4errors - $msg:\n"); my @errs = $p4->Errors(); foreach my $e (@errs){ convert::log($e); } } if ($p4->WarningCount()) { convert::log("**p4warnings - $msg:\n"); my @warn = $p4->Warnings(); foreach my $e (@warn){ convert::log($e); } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 7114 | Sven Erik Knop |
This is a modification of the public depot version of pvcsToP4. This version requires the official P4Perl release from the Perforce ftp site. The main change compared to the public depot release of pvcsToP4 is that this version supports branching - as far as I have been able to test - completely. Please see CHANGELOG and README for some details. |