#!/usr/bin/perl -w # # VSS to Perforce converter, phase III: construct Perforce depot # # Copyright 1998 Perforce Software. All rights reserved. # Written by James Strickland, April 1998 # # This script uses the metadata produced by earlier phases to direct a loop # which extracts VSS revisions and performs the required Perforce commands # to construct a Perforce depot corresponding to the (improved) VSS data. # # RHGC - Modifed to use new depot and depot_root as specified in config. # - Modified to use DB_File for speed with large data sets (and improve label handling) # - Modified to use P4Perl interface and VSS OLE Automation for speed. require 5.0; use strict; use integer; use lib '.'; use convert; use Change; use Env; use File::Path; use Win32::OLE; use Win32::OLE qw(in); use Win32::OLE::Const; use Win32::OLE::Variant; # Check for P4 module installed my $USEP4 = 0; my $p4; my $p4form; eval{use P4;}; if (!$@) { $USEP4 = 1; print "Using P4Perl interface\n"; $p4 = new P4; $p4->SetPort($convert::p4port); $p4->SetClient($convert::p4client); $p4->SetUser($convert::p4user); $p4->Init() or die( "Failed to connect to Perforce Server: $convert::p4port $!\n" ); $p4form = new P4; $p4form->SetPort($convert::p4port); $p4form->SetClient($convert::p4client); $p4form->SetUser($convert::p4user); $p4form->ParseForms(); $p4form->Init() or die( "Failed to connect to Perforce Server: $convert::p4port $!\n" ); } # See if we want to and can initialise VSS OLE interface my $VSSDB; if ($convert::vss_use_ole) { eval {$VSSDB = Win32::OLE->new('SourceSafe', 'Quit');}; if (!$@) { print "Using VSS OLE Automation interface\n"; my $SrcSafeIni = $ENV{"SSDIR"}."\\SRCSAFE.INI"; Win32::OLE->Option(Warn => 3); # Die on exceptions (with error message), e.g. if password wrong $VSSDB->Open($SrcSafeIni, $convert::vss_user, $convert::vss_password); } } # open all our input files my $msg="can't open for read"; open(FILES, "<$convert::metadata_dir/files") or die $msg; open(LABELS_SUMMARY, "<$convert::metadata_dir/labels_summary") or die $msg; open(CHANGES, "<$convert::metadata_dir/changes") 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 (%file_type, # maps archive -> file type # e.g. "$/foo" -> "text" # Now a DB_File %labels, # List of all labels %label_files, # maps "revision archive" -> list of labels # e.g. "1 $/foo" -> "itworks","bobsyouruncle" # Now a DB_File %added, # maps depot file -> true/false # e.g. "//depot/foo" -> 1 # Now a DB_File ); # Check for DB_File module installed - much faster for larger repositories if so my $USEDB_FILE = 0; eval{use DB_File;}; if (!$@) { $USEDB_FILE = 1; # Local read/write ones DB files tie %added, "DB_File", "$convert::metadata_dir/db.added", O_RDWR|O_CREAT, 0666, $DB_HASH or die "Cannot open file '$convert::metadata_dir/db.added': $!\n"; tie %file_type, "DB_File", "$convert::metadata_dir/db.file_type", O_RDWR|O_CREAT, 0666, $DB_HASH or die "Cannot open file '$convert::metadata_dir/db.file_type': $!\n"; # This one is created by readhist.pl tie %label_files, "DB_File", "$convert::metadata_dir/db.labels", O_RDONLY, $DB_HASH or die "Cannot open file '$convert::metadata_dir/db.labels': $!\n"; } # initialize %file_type while(<FILES>) { chomp; my ($file_type,$file) = split(/ /,$_,2); $file_type{$file} = $file_type; } close(FILES); # initialize %label and run p4 label once for each label which is not a # "delete" label while(<LABELS_SUMMARY>) { chomp; my ($label,$timestamp) = split(/ /,$_,2); my $comment = ''; $comment .= <LABELS_SUMMARY> while ($comment !~ /\*{10}\n/); $comment =~ s/\*{10}\n$//; $comment = "vsstop4 label" if ($comment =~ /^\s*$/); # Set default comment. my $label_view = "//$convert::depot/$convert::depot_root/..."; # adjust the update time of the label my (@tm,$date); @tm=localtime($timestamp); $date=sprintf("%4d/%02d/%02d %02d:%02d:%02d",(($tm[5]>=70) ? $tm[5]+1900 : $tm[5]+2000), $tm[4]+1,$tm[3],$tm[2],$tm[1],$tm[0]); if (!$USEP4) { my $form=convert::p4run(" label -o $label"); # label names cannot contain spaces $comment =~ s@\n@\n\t@gs; # Update the Description field in the label. $form =~ s@(\nDescription:\s*)\n\s+\S[^\n]*\n@$1\n\t$comment@s; # Create a more specific view for the label - not just all depots which is the default. $form =~ s@(\nView:\s*)\n\s+.*\n$@$1\n\t$label_view@s; if ($form =~ /\nUpdate:[^\n]*/) { $form =~ s@\nUpdate:[^\n]*@\nUpdate: $date@s; convert::log("Label input:\n$form"); } else { $form =~ s@(\nLabel:.*?\n)@$1\nUpdate: $date\n@s; } convert::p4run(" label -i",$form); } else { my ($form, @view, @result); $form = $p4form->FetchLabel($label); $form->{'Description'} = $comment; push @view, $label_view; $form->{'View'} = \@view; $form->{'Update'} = $date; @result = $p4form->SaveLabel($form); convert::log("Label update:\n".join("\n", @result)); print_p4errors($p4form, "Updating label $label"); } $labels{$label} = 1; unlink ("$convert::metadata_dir/labels/$label"); } close(LABELS_SUMMARY); # print timestamp print "Depot creation started " . scalar(localtime()) . "\n"; # For each change, # - retrieve revisions from VSS using 'get' # - mark files as added or edited with 'p4 add' or 'p4 edit', # followed by 'p4 submit' # - update list of file#rev associated with any pertinent labels my $start_time = $convert::start_time; my ($c,$op); CHG: while( $c = get Change(\*CHANGES) ) { my ($change_number,@checkins); # Ignore change if a start_time is specified if ($start_time > $c->timestamp) { print "Ignoring changelist with timestamp: ". $c->datetime ."\n"; next CHG; } my $index; foreach $index (@{$c->changelist}) { my $p4rev; my ($revision,$vss_file) = split(/ /,$index,2); my ($client_rel_dir,$client_file) = convert::p4dir_and_file( $vss_file); my $client_dir = convert::join_paths( $convert::client_root, $client_rel_dir ); # create all needed directories on the path mkpath($client_dir); # 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_dir, $client_file ); # get the file from VSS if ($VSSDB) { unlink("${client_dir}/${client_file}"); my $item = $VSSDB->VSSItem($vss_file, 0); my $version = $item->Version(($file_type{$vss_file} eq "tempobj") ? "" : "$revision"); $version->Get("${client_dir}/${client_file}"); } else { convert::get_vss_file($vss_file,($file_type{$vss_file} eq "tempobj") ? "" : $revision,$client_dir,$client_file); } # see if the file is indeed there now if (-f "${client_dir}/${client_file}") { # schedule p4 add or edit operation # depending on whether file has already been added push(@checkins, [ $vss_file,$revision,$depot_file ] ); # RHGC - because of restartability, add extra check if($added{$depot_file}) { p4exec($depot_file, "edit") =~ /#(\d+) - opened for edit/ or die "p4 edit $depot_file failed"; $p4rev=$1 +1; } elsif (p4exec($depot_file, "fstat") =~ /depotFile/) { p4run($depot_file, "edit") =~ /#(\d+) - opened for edit/ or die "p4 edit $depot_file failed"; $added{$depot_file}=1; $p4rev=$1 +1; } else { my @addcmd = ("add", "-t", "$file_type{$vss_file}"); if ($convert::typemap_regexp) { # don't specify type when file is handled with p4 typemap if ($depot_file =~ /$convert::typemap_regexp/) { @addcmd = ("add"); } else { print "file $depot_file not matched by typemap.\n"; } } p4exec($depot_file, @addcmd) =~ /opened for add/ or die "p4 add $depot_file failed"; $added{$depot_file}=1; $p4rev=1; } # check if this revision is labelled if(exists($label_files{$index})) { my $labels = $label_files{$index}; my @label_list = split(/ /, $labels); for (@label_list) { open(LABELFILE,">>$convert::metadata_dir/labels/$_") or die "can't open label file $_: $!\n"; print LABELFILE "$depot_file#$p4rev\n"; close(LABELFILE); } } } else { # the file isn't there - the ss get failed if ($convert::skip_ss_get_errors) { print STDERR "ERROR: VSS file not found: ${vss_file}#${revision}\n"; } else { die "get_vss_file() revision $revision to $client_dir/$client_file failed"; } } } # submit the change, and write out the association between VSS # archive and revision number and Perforce file and change number $change_number = p4submit($c); # submit the change my $checkin; foreach $checkin (@checkins) { my ($vss_file,$r,$depot_filename) = @$checkin; # write change number#filename to avoid confusion with filename#revision print MAPPING "$r#$change_number#$depot_filename#$vss_file\n"; # NOTE: $vss_file has to go last since it can contain # characters } } # now do the labelling print "\nLabelling started " . scalar(localtime()) . "\n"; for (keys(%labels)) { convert::p4run(" -x $convert::metadata_dir/labels/$_ labelsync -l $_"); } if ($USEDB_FILE) { untie %label_files; untie %added; untie %file_type; } if ($USEP4) { $p4->Final(); $p4form->Final(); } # 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); return @result if (!@result); $r = join(" ", @result); return $r; } else { return convert::p4run(join(" ", @cmd) . "\"$fname\"") } } # Run appropriate version of submit sub p4submit { my $c = shift; my ($change_number); if (!$USEP4) { $change_number = $c->submit; # submit the change } else { my $change_description = $c->change_description; my ($form,$output,@result); $form = $p4form->FetchChange(); if ($form->{'Files'}) { $form->{'Description'} = $change_description; @result = $p4form->SaveSubmit($form); $output = join("\n", @result); convert::log("Submit result:\n$output\n"); print_p4errors($p4form, "Submitting change"); # 2 forms of result - check for which one and extract the resulting change number. if( $output =~ m/Change ([0-9]+) submitted./si ) { $change_number = $1; } elsif ( $output =~ m/Change [0-9]+ renamed change ([0-9]+) and submitted./si ) { $change_number = $1; } else { die "p4 submit aborted - conversion terminated. Output was:\n$output"; } # fix date, user on submitted change my $user = $c->author; my $date = $c->datetime; $form = $p4form->FetchChange($change_number); $form->{'Date'} = $date; $form->{'User'} = $user; @result = $p4form->SaveChange($form, "-f"); print_p4errors($p4form, "Updating change"); print "Change $change_number submitted.\r"; # running total.. } else { print "WARNING: Change $change_number 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"; } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#14 | 3607 | Robert Cowham |
Put use of DB_File inside eval so it doesn't matter if not installed. Updated README for Performance Tips. |
||
#13 | 3604 | Robert Cowham |
Added VSS OLE Automation back in but as a configurable option. Tidied up logging. |
||
#12 | 3603 | Robert Cowham | Added back VSS OLE Automation with check for if not installed. | ||
#11 | 3601 | Robert Cowham | Use P4Perl interface if installed. | ||
#10 | 3593 | Robert Cowham | Shifted 2 more hashes into DB files. | ||
#9 | 3592 | Robert Cowham | Back out previous change as it is slower (but leave for posterity)... | ||
#8 | 3591 | Robert Cowham |
Example using VSS OLE Automation to get versions. Unfortunately this seems slower than spawning ss.exe from command line - go figure.... |
||
#7 | 3590 | Robert Cowham | Use DB_File to store label info rather than large hashes in memory. | ||
#6 | 3589 | Robert Cowham | Merge in changes from Main branch. | ||
#5 | 2046 | Robert Cowham |
Add logging of a couple of extra things. Fix problem with spaces in author names. |
||
#4 | 1702 | Robert Cowham |
Extra check re adding new files or editing existing ones. Also prompt for confirmation if start_time specified. |
||
#3 | 1695 | Robert Cowham | Integrated Peter's changes in - note not fully tested... | ||
#2 | 1691 | Robert Cowham |
Latest changes, e.g. - allow restarts (from date and time) - add client name to config options |
||
#1 | 237 | Robert Cowham |
Improved version of vsstop4. Makes life easier if importing into a depot which already contains stuff you want to keep. Also handles other people updating the depot at the same time. See changes labelled RHGC, and new items in config file. |