# This module contains all code common to different phases of the # VSS to Perforce converter. # # $Id: //guest/perforce_software/utils/vsstop4/newp4perl/convert.pm#6 $ # require 5.0; package convert; use strict; use vars qw(@ISA @EXPORT); use integer; use Carp; use Cwd; use Time::Local; use POSIX qw(strftime); use File::Path; use Win32::Registry; use IO::Handle; require Exporter; @ISA = qw(Exporter); @EXPORT = qw( $metadata_dir $data_dir $root $client_root $time_interval $lowercase_pathnames $lowercase_filenames $lowercase_usernames $lowercase_extensions $typemap_regexp $perform_verify $skip_ss_get_errors $bypass_metadata $debug_level $start_time $p4port $p4client $p4user $port log sum_log p4run run p4dir_and_file p4escape_fname p4unescape_fname format_str $label_end_marker $format_date $format_time $exclude $vsscorrupt $label_ignore_regexp $ss_options $vss_use_ole $vss_user $vss_password); # set values of global variables $convert::metadata_dir = "metadata"; $convert::data_dir = "data"; # Set up global logfile and make sure it's autoflush on in case of crash my $logfile_name = "logfile.log"; if (-f $logfile_name) { my $modtime = (stat($logfile_name))[9]; my $suffix = strftime("%Y%m%d%H%M%S", localtime($modtime)); rename($logfile_name, "logfile-$suffix.log"); } open(LOGFILE,">>$logfile_name") or die "can't open logfile: $!"; LOGFILE->autoflush(1); # read the configuration file and set up needed variables my %option; if (defined($ENV{VSSTOP4_CONFIG})) { %option = read_form($ENV{VSSTOP4_CONFIG}); } else { %option = read_form("config.ini"); } my ($date_format, $time_format) = read_date_time_format_from_registry(); die "must specify root" unless exists($option{'root'}); $convert::root = forward_slash($option{'root'}); $convert::exclude = get_option('exclude', ""); $convert::vsscorrupt = get_yn_option('vsscorrupt'); $convert::depot = get_option('depot', "depot"); # Main Perforce parameters P4PORT/CLIENT/USER $convert::p4client = get_option('p4client', $ENV{P4CLIENT}); $convert::p4port = get_option('p4port', $ENV{P4PORT}); $convert::p4user = get_option('p4user', $ENV{P4USER}); $convert::label_prefix = get_option('label_prefix', ""); $convert::label_ignore_regexp = get_option('label_ignore_regexp', ""); # The first line of the depot $convert::depot_root = (exists($option{'depot_root'})) ? check_root($option{'depot_root'}) : check_root("main"); $convert::client_root = forward_slash(cwd() . "/" . $convert::data_dir); $convert::time_interval = get_option('time_interval', 600); $convert::lowercase_pathnames = get_yn_option('lowercase_pathnames'); $convert::typemap_regexp = get_option("typemap_regexp", ""); $convert::lowercase_filenames = get_yn_option('lowercase_filenames'); $convert::lowercase_extensions = get_yn_option('lowercase_extensions'); $convert::lowercase_usernames = get_yn_option('lowercase_usernames'); $convert::ss_options = make_ss_options(); $convert::vss_use_ole = get_yn_option('vss_use_ole'); # Text to mark end of a label when processing labels in metadata files $convert::label_end_marker = "==========VSSTOP4-END-OF-LABEL"; $convert::vss_user = get_option("vss_user", ""); $convert::vss_password = get_option("vss_password", ""); $convert::debug_level = get_option('debug_level', 0); $convert::bypass_metadata = get_yn_option('bypass_metadata'); $convert::perform_verify = get_yn_option('perform_verify'); $convert::skip_ss_get_errors = get_yn_option('skip_ss_get_errors'); # Set up by reading registry $convert::format_date = ($date_format); $convert::format_time = ($time_format); # If start_time is specified then convert to timestamp, 0 means process all changes $convert::start_time = (exists($option{'start_time'})) ? mktimestamp($option{'start_time'}) : 0; # Get a standard option sub get_option { my ($name, $default) = @_; return exists($option{$name}) ? $option{$name} : $default; } # Get a standard option sub get_yn_option { my ($name) = @_; return (exists($option{$name})) ? ($option{$name} =~ /y/i) : 0; } # Check valid options including user/password sub make_ss_options { my $options = get_option("ss_options", ""); $options .= " -Y" . get_option("vss_user", "") . "," . get_option("vss_password", ""); return $options; } # Ensure it has trailing slash sub check_root { my $root = shift; if ($root eq "") { } elsif($root !~ /\/$/) { $root .= "/"; } return $root; } # Run a command, optionally piping a string into it on stdin. # Returns whatever the command printed to stdout. The whole thing is # optionally logged. NOTE that stderr is not redirected. sub run { my ($syscall,$stuff_to_pipe_in) = @_; my $result; if(defined($stuff_to_pipe_in)) { # Use a temporary file because not all systems implement pipes open(TEMPFILE,">pipeto") or die "can't open pipeto: $!\n"; print TEMPFILE $stuff_to_pipe_in; close(TEMPFILE); $result = `$syscall <pipeto`; unlink("pipeto"); } else { $result = `$syscall 2>&1`; } if($convert::debug_level > 0) { print LOGFILE "\n\nCommand: $syscall\n"; print LOGFILE $result; } return $result; } sub format_str { my $data = shift; my $result = ""; if (ref($data) eq 'ARRAY') { foreach my $i (@$data) { $result .= format_str($i); } } elsif (ref($data) eq 'HASH') { foreach my $k (keys %$data) { $result .= "$k: " . format_str($data->{$k}); } } else { $result = "$data\n"; } return $result; } sub log { my $data = shift; print LOGFILE "Log: " . format_str($data); } # Print to summary log file sub sum_log { my $data = shift; open(SUMLOGFILE,">>sumlogfile.log") or die "can't open sumlogfile: $!"; print SUMLOGFILE "$data\n"; close(SUMLOGFILE); } # Run a p4 command - specifying p4 environment explicitly sub p4run { my ($cmd,$stuff_to_pipe_in) = @_; my $p4cmd = "p4 -p " . $convert::p4port . " -c " . $convert::p4client . " -u " . $convert::p4user . " " . $cmd; if (defined($stuff_to_pipe_in)) { return run($p4cmd, $stuff_to_pipe_in); } else { return run($p4cmd); } } # Convert from "yyyy/mm/dd hh:mm:ss" to timestamp sub mktimestamp { my $date=shift; my ($year,$month,$day,$hour,$min,$sec); die "can't parse start_timestamp" unless (($year,$month,$day,$hour,$min,$sec) = ($date =~ m@(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)@)); my $timestamp=timelocal($sec, $min, $hour, $day, $month - 1, $year); return $timestamp; } sub read_form # read a Perforce style form { my $file = shift; my (%hash,$current_keyword,$value); open(F,"<$file") or croak("can't open $file: $!"); while(<F>) { s/^\s*#.*$//; # kill comments and any whitespace preceding the comment if(/^$/) { # empty line or line with just a comment undef($current_keyword); } elsif(substr($_,0,1) eq "\t") { croak("unrecognized line") if(!defined($current_keyword)); s/^\t//; $hash{$current_keyword} .= $_; } elsif(/(.*?):\s*(.*)/) { # keyword is everything up to the *first* colon $current_keyword = $1; $value = $2; $value =~ s/\s*$//; # Trim white space at end. $hash{$current_keyword} = $value; } } close(F); return %hash; } sub forward_slash { my $s = shift; $s =~ s@\\@/@g; return $s; } # p4dir_and_file computes the directory and filename relative to the client root # given the VSS filename. sub p4dir_and_file { my $client_rel_dir = shift; $client_rel_dir =~ s%[\000-\031]%_%g; # convert unprintable to _ $client_rel_dir =~ s@^\$/@$convert::depot_root@; $client_rel_dir =~ s@/([^/]*)$@@; # strip off filename my $client_file = $1; $client_rel_dir = lc($client_rel_dir) if($convert::lowercase_pathnames); $client_file = lc($client_file ) if($convert::lowercase_filenames); if($convert::lowercase_extensions) { $client_file =~ /(.*?)(\.[^.]+)?$/; my ($cl_base,$cl_ext) = ($1,$2); $cl_ext = lc($cl_ext); $client_file = $cl_base . $cl_ext; } return ($client_rel_dir,$client_file); } # return a string joining the pathname components, # ensuring there is one / between components and there is no trailing slash sub join_paths { my $path=""; for (@_) { if(defined($_) && $_ ne "") { $path .= (substr($_,-1) eq '/') ? $_ : $_ . '/'; } } chop($path) if(substr($path,-1) eq '/'); return $path; } # get the given file from VSS (specified as a complete "project" path) # and name it according to its name in Perforce (given client dir and filename) sub get_vss_file { my ($vss_file,$revision,$client_dir,$client_file) = @_; mkpath($client_dir); my $client_filepath = join_paths($client_dir, $client_file); unlink($client_filepath); $revision = "-v$revision" if $revision; # if $revision not set, don't give -v flag (tempobj hack) convert::run("ss get \"$vss_file\" -W -GL\"${client_dir}\" $revision $convert::ss_options"); # get it writable so that we can unlink it later $vss_file =~ m@/([^/]*)$@; my $vss_filepath = join_paths($client_dir, $1); rename($vss_filepath,$client_filepath) unless($vss_filepath eq $client_filepath); } # Escape perforce filename sub p4escape_fname { my $file = shift; $file =~ s/%/%25/g; # must come first! $file =~ s/@/%40/g; $file =~ s/#/%23/g; $file =~ s/\*/%2a/g; return $file; } sub p4unescape_fname { my $file = shift; $file =~ s/%40/@/g; $file =~ s/%23/#/g; $file =~ s/%2a/\*/g; $file =~ s/%25/%/g; return $file; } # Read current date/time format settings from Registry and parse appropriately sub read_date_time_format_from_registry { my $Register = "Control Panel\\International"; my ($hkey, %values, $key); $::HKEY_CURRENT_USER->Open($Register,$hkey)|| die $!; $hkey->GetValues(\%values); $hkey->Close(); my $sShortDate = $values{"sShortDate"}->[2]; # Date format, e.g. dd/MM/yyyy my $iTime = $values{"iTime"}->[2]; # 0 = 12 hour, 1 = 24 hour format my $sTime = $values{"sTime"}->[2]; # time seperator (usually ":") my $sDate = $values{"sDate"}->[2]; # Date seperator (usually "/") my $s1159 = $values{"s1159"}->[2]; # AM indicator (if required) my $s2359 = $values{"s2359"}->[2]; # PM indicator (if required) # Remove duplicates of d/M/y and change to lowercase $sShortDate =~ s/d+/d/i; $sShortDate =~ s/m+/m/i; $sShortDate =~ s/y+/y/i; my $TimeFormat = "H" . $sTime . "M"; $TimeFormat .= "p" if $iTime == 0; &log("Date Format: $sShortDate\n"); &log("Time Format: $TimeFormat\n"); return $sShortDate, $TimeFormat; } 1;
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#6 | 6399 | Robert Cowham | Tidy label end markers. | ||
#5 | 6392 | Robert Cowham | Catch stderr - avoids vss problems. | ||
#4 | 6391 | Robert Cowham | Got convert running through. | ||
#3 | 6390 | Robert Cowham | Remove debug info | ||
#2 | 6389 | Robert Cowham | Rotate logfile name. | ||
#1 | 6388 | Robert Cowham | Populate branch | ||
//guest/perforce_software/utils/vsstop4/main/convert.pm | |||||
#19 | 6244 | Robert Cowham |
- Merge in changes to cope with previously illegal chars in filenames %, *, #, @ (requires 2004.2 server and above) |
||
#18 | 5860 | Robert Cowham | - Improved logging slightly - new summary log file sumlogfile.log created | ||
#17 | 5681 | Robert Cowham |
- Fix compatibility problem with 2005.1 server - Added tests for all servers back to 2003.2 - Log more info about options used - Clarify the vss_user/password rather than put in ss_options |
||
#16 | 5308 | Robert Cowham | Make name of config file settable via environment VSSTOP4_CONFIG | ||
#15 | 5306 | Robert Cowham |
- Minor fix of error handling for non-scalar values - allow for depot_root to be empty - change convert.pl to use P4Perl only |
||
#14 | 4026 | Robert Cowham |
Make previous change configurable rather than hard-coding and add explanation to config.ini about it. Point Update section on web page to Changelog.txt |
||
#13 | 4024 | Robert Cowham | Make slightly more robust - ignore VSS prompts for input by using the -I- flag to commands. | ||
#12 | 4000 | Robert Cowham |
Remove changes for non-P4Perl version. Improved Web page with download links. |
||
#11 | 3925 | Robert Cowham | Problems as per Michael Shields when not using P4Perl. | ||
#10 | 3918 | Robert Cowham | Improve performance of logging. | ||
#9 | 3797 | Robert Cowham |
Remove warnings. Trim whitespace from ends of lines in config.ini entries |
||
#8 | 3639 | Robert Cowham |
Changed filetype and inserted ID string. Fixed typo. |
||
#7 | 3609 | Robert Cowham |
Integrate in changes for: - Use of DB_File for large hashes (if available) - Reworking of label algorithm - MUCH faster - Use of P4Perl (if available) - much faster - Use of VSS OLE Automation (if available) - sometimes faster See README.txt and Performance section for details. |
||
#6 | 3558 | Robert Cowham |
Fixed bug with French date formats. Autoupdate client root and view based on config.ini entries. |
||
#5 | 3553 | Robert Cowham |
Read date and time formats directly from Windows registry. Avoids problems with users forgetting to set or getting wrong. |
||
#4 | 2660 | Robert Cowham |
Allow labels to be ignored if not wanted using configurable regexp. Next change in this area is to make recreation of labels vastly quicker! |
||
#3 | 2167 | Robert Cowham |
Renamed files to use Windows conventions as most likely platform for a VSS conversion. |
||
#2 | 2165 | Robert Cowham | Merged in changes from Guest branch | ||
#1 | 2160 | Robert Cowham | Main version from .zip file from http://www.perforce.com/perforce/loadsupp.html#conv page |