14 our(%gCfg, %gSth, @gErr, %gFh, $gSysOut, %gActionType, %gNameLookup, $gId,
17 our $VERSION = '0.10';
30 ###############################################################################
32 ###############################################################################
35 # store a hash of actions to take; allows restarting in case of failed
39 INIT => {handler
=> sub{ 1; },
40 next => 'LOADVSSNAMES'},
42 # Load the "real" names associated with the stored "short" names
43 LOADVSSNAMES
=> {handler
=> \
&LoadVssNames
,
44 next => 'FINDDBFILES'},
46 # Add a stub entry into the Physical table for each physical
48 FINDDBFILES
=> {handler
=> \
&FindPhysDbFiles
,
49 next => 'GETPHYSHIST'},
51 # Load the history of what happened to the physical files. This
52 # only gets us halfway there because we don't know what the real
54 GETPHYSHIST
=> {handler
=> \
&GetPhysVssHistory
,
55 next => 'MERGEPARENTDATA'},
57 # Merge data from parent records into child records where possible
58 MERGEPARENTDATA
=> {handler
=> \
&MergeParentData
,
59 next => 'BUILDACTIONHIST'},
61 # Take the history of physical actions and convert them to VSS
63 BUILDACTIONHIST
=> {handler
=> \
&BuildVssActionHistory
,
66 # Combine these individual actions into atomic actions a' la SVN
67 BUILDREVS
=> {handler
=> \
&BuildRevs
,
70 # Create a dumpfile or import to repository
71 IMPORTSVN
=> {handler
=> \
&ImportToSvn
,
77 while ($gCfg{task
} ne 'DONE') {
78 $info = $joblist{ $gCfg{task
} }
79 or die "FATAL ERROR: Unknown task '$gCfg{task}'\n";
81 print "TASK: $gCfg{task}\n";
84 print "Press ENTER to continue...\n";
86 die if $temp =~ m/^quit/i;
89 &{ $info->{handler
} };
90 &SetSystemTask
( $info->{next} );
96 ###############################################################################
98 ###############################################################################
100 &DoSsCmd
("info -a \"$gCfg{vssdatadir}\\names.dat\" -s xml");
102 my $xs = XML
::Simple
->new(KeyAttr
=> [],
103 ForceArray
=> [qw(Entry)],);
105 my $xml = $xs->XMLin($gSysOut);
107 my $namesref = $xml->{NameCacheEntry
} || return 1;
109 my($entry, $count, $offset, $name);
111 &StartDataCache
('NameLookup', 1);
114 foreach $entry (@
$namesref) {
115 $count = $entry->{NrOfEntries
};
116 next ENTRY
unless $count > 1;
118 $offset = $entry->{offset
};
121 $name = $entry->{Entry
}->[1]->{content
};
123 $name = $entry->{Entry
}->[$count - 2]->{content
};
126 &AddDataCache
($offset, $name);
133 ###############################################################################
135 ###############################################################################
136 sub FindPhysDbFiles
{
138 &StartDataCache
('Physical', 1);
140 find
(\
&FoundSsFile
, $gCfg{vssdatadir
});
144 } # End FindPhysDbFiles
146 ###############################################################################
148 ###############################################################################
151 my $path = $File::Find
::name
;
152 return if (-d
$path);
154 my $vssdatadir = quotemeta($gCfg{vssdatadir
});
156 if ($path =~ m
:^$vssdatadir/./([a
-z
]{8})$:i
) {
157 &AddDataCache
(uc($1));
162 ###############################################################################
164 ###############################################################################
165 sub GetPhysVssHistory
{
166 my($sql, $sth, $row, $physname, $physdir);
169 &StartDataCache
('PhysicalAction', 1, 1);
171 $sql = "SELECT * FROM Physical";
172 $sth = &PrepSql
($sql);
175 my $xs = XML
::Simple
->new(ForceArray
=> [qw(Version)]);
177 while (defined($row = $sth->fetchrow_hashref() )) {
178 $physname = $row->{physname
};
180 $physdir = "$gCfg{vssdir}\\data\\" . substr($physname, 0, 1);
182 &GetVssPhysInfo
($physdir, $physname, $xs);
187 } # End GetPhysVssHistory
189 ###############################################################################
191 ###############################################################################
193 my($physdir, $physname, $xs) = @_;
195 &DoSsCmd
("info -a \"$physdir\\$physname\" -s xml");
197 my $xml = $xs->XMLin($gSysOut);
200 if (defined($xml->{ProjectItem
})) {
201 $parentphys = ($physname eq 'AAAAAAAA')?
202 '' : &GetProjectParent
($xml);
203 } elsif (defined($xml->{FileItem
})) {
204 $parentphys = &GetFileParent
($xml);
206 &ThrowWarning
("Can't handle file '$physname'; not a project or file\n");
210 &GetVssItemInfo
($physname, $parentphys, $xml);
212 } # End GetVssPhysInfo
214 ###############################################################################
216 ###############################################################################
217 sub GetProjectParent
{
220 no warnings
'uninitialized';
221 return $xml->{ProjectItem
}->{ParentPhys
} || undef;
223 } # End GetProjectParent
225 ###############################################################################
227 ###############################################################################
231 # TODO: determine whether we ever really need to get the parent for a child
232 # item at this phase. For commits, we'll apply the change to all existing
233 # shares at that time, and for renames, deletes, shares, etc., we'll have
234 # that info from the parent already.
238 no warnings
'uninitialized';
240 my $parents = $xml->{ParentFolder
};
243 if (ref $parents eq 'ARRAY') {
244 # If there is more than one parent folder, this is a shared or branched
245 # item. Since the child item has no way of knowing who its original
246 # parent is, we'll leave it blank and expect it to be filled in by the
250 $parentphys = $parents->{ParentPhys
} || undef;
255 } # End GetFileParent
257 ###############################################################################
259 ###############################################################################
261 my($physname, $parentphys, $xml) = @_;
263 return 0 unless defined $xml->{Version
};
265 my($parentdata, $version, $number, $action, $name, $actionid, $actiontype,
266 $tphysname, $itemname, $itemtype, $parent, $user, $timestamp, $comment,
267 $info, $priority, $cachename);
270 foreach $version (@
{ $xml->{Version
} }) {
271 $action = $version->{Action
};
272 $name = $action->{SSName
};
273 $tphysname = $action->{Physical
} || $physname;
274 $user = $version->{UserName
};
275 $timestamp = $version->{Date
};
277 $itemname = $name->{content
};
279 if (defined($name->{offset
})) {
280 # Might have a "better" name in the name cache, but sometimes the
281 # original name is best.
282 if ($name->{offset
} == 39080) {
286 $cachename = $gNameLookup{ $name->{offset
} };
288 if (!defined($itemname) || ($itemname =~ m/~/ &&
289 length($cachename) > length($itemname))) {
291 print "Changing name of '$itemname' to '$cachename' from "
292 . "name cache\n" if $gCfg{debug
};
294 $itemname = $cachename;
296 print "Found name '$cachename' in namecache, but kept original "
297 . "'$itemname'\n" if $gCfg{debug
};
303 $actionid = $action->{ActionId
};
304 $info = $gActionType{$actionid}; # || next VERSION; # unknown action
308 $itemtype = $info->{type
};
309 $actiontype = $info->{action
};
316 if ($version->{Comment
} && !ref($version->{Comment
})) {
317 $comment = $version->{Comment
} || undef;
320 if ($itemtype == 1 && $physname eq 'AAAAAAAA' && ref($tphysname)) {
321 $tphysname = $physname;
323 } elsif ($physname ne $tphysname) {
324 # If version's physical name and file physical name are different,
325 # this is a project describing an action on a child item. Most of
326 # the time, this very same data will be in the child's physical
327 # file and with more detail (such as check-in comment).
329 # However, in some cases (such as renames, or when the child's
330 # physical file was later purged), this is the only place we'll
331 # have the data; also, sometimes the child record doesn't even
332 # have enough information about itself (such as which project it
333 # was created in and which project(s) it's shared in).
335 # So, for a parent record describing a child action, we'll set a
336 # flag, then combine them in the next phase.
340 # OK, since we're describing an action in the child, the parent is
341 # actually this (project) item
343 $parentphys = $physname;
346 if ($itemtype == 1) {
350 if ($actiontype eq 'RENAME') {
351 # if a rename, we store the new name in the action's 'info' field
352 no warnings
'uninitialized';
354 $name = $action->{NewSSName
};
355 if (defined($name->{offset
})) {
356 $info = $gNameLookup{ $name->{offset
} } || $name->{content
};
358 $info = $name->{content
} || undef;
361 if ($itemtype == 1) {
366 $number = ($parentdata)?
undef : $version->{VersionNumber
};
368 $priority -= 4 if $actiontype eq 'ADD'; # Adds are always first
369 $priority -= 3 if $actiontype eq 'COPY';
371 &AddDataCache
($tphysname, $number, $parentphys, $actiontype, $itemname,
372 $itemtype, $timestamp, $user, $info, $priority,
373 $parentdata, $comment);
377 } # End GetVssItemInfo
379 ###############################################################################
381 ###############################################################################
385 $sth = &PrepSql
('SELECT offset, name FROM NameLookup');
388 while(defined($row = $sth->fetchrow_hashref() )) {
389 $gNameLookup{ $row->{offset
} } = $row->{name
};
391 } # End LoadNameLookup
393 ###############################################################################
395 ###############################################################################
396 sub MergeParentData
{
397 # VSS has a funny way of not placing enough information to rebuild history
398 # in one data file; for example, renames are stored in the parent project
399 # rather than in that item's data file. Also, it's sometimes impossible to
400 # tell from a child record which was eventually shared to multiple folders,
401 # which folder it was originally created in.
403 # So, at this stage we look for any parent records which described child
404 # actions, then update those records with data from the child objects. We
405 # then delete the separate child objects to avoid duplication.
407 my($sth, $rows, $row);
408 $sth = &PrepSql
('SELECT * FROM PhysicalAction WHERE parentdata = 1');
411 # need to pull in all recs at once, since we'll be updating/deleting data
412 $rows = $sth->fetchall_arrayref( {} );
414 my($childrecs, $child, $id);
417 foreach $row (@
$rows) {
418 $childrecs = &GetChildRecs
($row);
420 if (scalar @
$childrecs > 1) {
421 &ThrowWarning
("Multiple child recs for parent rec "
422 . "'$row->{action_id}'");
425 foreach $child (@
$childrecs) {
426 &UpdateParentRec
($row, $child);
427 push(@delchild, $child->{action_id
});
431 foreach $id (@delchild) {
432 &DeleteChildRec
($id);
437 } # End MergeParentData
439 ###############################################################################
441 ###############################################################################
458 my $sth = &PrepSql
($sql);
459 $sth->execute( @
{ $parentrec }{qw(physname actiontype timestamp author)} );
461 return $sth->fetchall_arrayref( {} );
464 ###############################################################################
466 ###############################################################################
467 sub UpdateParentRec
{
468 my($row, $child) = @_;
470 # The child record has the "correct" version number (relative to the child
471 # and not the parent), as well as the comment info
483 my $sth = &PrepSql
($sql);
484 $sth->execute( $child->{version
}, $child->{comment
}, $row->{action_id
} );
486 } # End UpdateParentRec
488 ###############################################################################
490 ###############################################################################
494 my $sql = "DELETE FROM PhysicalAction WHERE action_id = ?";
496 my $sth = &PrepSql
($sql);
498 } # End DeleteChildRec
500 ###############################################################################
501 # BuildVssActionHistory
502 ###############################################################################
503 sub BuildVssActionHistory
{
504 &StartDataCache
('VssAction', 1, 1);
506 my($sth, $row, $action, $handler, $itempaths, $itempath);
508 $sth = &PrepSql
('SELECT * FROM PhysicalAction ORDER BY timestamp ASC, '
514 ADD
=> \
&VssAddHandler
,
515 RENAME
=> \
&VssRenameHandler
,
516 COPY
=> \
&VssCopyHandler
,
517 DELETE
=> \
&VssDeleteHandler
,
518 RECOVER
=> \
&VssRecoverHandler
,
521 while(defined($row = $sth->fetchrow_hashref() )) {
522 $action = $row->{actiontype
};
524 $handler = $handlers{$action};
526 if (defined($gPhysInfo{ $row->{physname
}} ) &&
527 $gPhysInfo{ $row->{physname
} }->{type
} != $row->{itemtype
} ) {
529 &ThrowError
("Inconsistent item type for '$row->{physname}'; "
530 . "'$row->{itemtype}' unexpected");
533 if ($row->{physname
} eq 'YAAAAAAA') {
537 # The handler's job is to keep %gPhysInfo up to date with physical-to-
538 # real item name mappings and return the full item paths of the physical
539 # item. In case of a rename, it will return the old name, so we then do
540 # another lookup on the new name.
542 # Most actions can actually be done on multiple items, if that item is
543 # shared; since SVN has no equivalent of shares, we replicate this by
544 # applying commit actions to all shares.
546 if (defined($handler)) {
547 $itempaths = &$handler($row);
549 $itempaths = &GetCurrentItemPaths
($row->{physname
});
552 if ($row->{actiontype
} eq 'RENAME') {
553 $row->{info
} = &GetCurrentItemName
($row->{physname
});
554 } elsif ($row->{actiontype
} eq 'COPY') {
555 $row->{info
} = &GetCurrentItemPaths
($row->{physname
}, 1)->[0];
558 foreach $itempath (@
$itempaths) {
559 $row->{itempath
} = $itempath;
561 &AddDataCache
(@
$row{ qw(physname version actiontype itempath itemtype
562 timestamp author info comment) });
569 } # End BuildVssActionHistory
571 ###############################################################################
573 ###############################################################################
577 # For each physical item, we store its "real" physical parent in the
578 # 'parentphys' property, then keep a list of additional shared parents in
579 # the 'sharedphys' array.
581 $gPhysInfo{ $row->{physname
} } =
583 type
=> $row->{itemtype
},
584 name
=> $row->{itemname
},
585 parentphys
=> $row->{parentphys
},
589 # File was just created so no need to look for shares
590 return &GetCurrentItemPaths
($row->{physname
}, 1);
591 } # End VssAddHandler
593 ###############################################################################
595 ###############################################################################
596 sub VssRenameHandler
{
599 # Get the existing paths before the rename; parent sub will get the new
600 # name and apply it to all existing paths
601 my $physname = $row->{physname
};
602 my $itempaths = &GetCurrentItemPaths
($physname);
604 my $physinfo = $gPhysInfo{$physname};
606 if (!defined $physinfo) {
607 &ThrowError
("Attempt to rename unknown item '$physname':\n"
608 . $gCfg{nameResolveSeen
});
611 # A rename of an item renames it in all its shares, so we can just change
612 # the name in one place
613 $physinfo->{name
} = $row->{info
};
616 } # End VssRenameHandler
618 ###############################################################################
620 ###############################################################################
624 my $physname = $row->{physname
};
625 my $physinfo = $gPhysInfo{$physname};
627 if (!defined $physinfo) {
628 &ThrowError
("Attempt to rename unknown item '$physname':\n"
629 . $gCfg{nameResolveSeen
});
632 push @
{ $physinfo->{sharedphys
} }, $row->{parentphys
};
634 # We only return only the path for this new location (the copy target);
635 # the source path will be added to the "info" field by caller
636 my $parentpaths = &GetCurrentItemPaths
($row->{parentphys
}, 1);
637 return [$parentpaths->[0] . $physinfo->{name
}];
639 } # End VssCopyHandler
641 ###############################################################################
643 ###############################################################################
644 sub VssDeleteHandler
{
647 # For a delete operation we return only the "main" path, since any deletion
648 # of shared paths will have their own entry
649 my $physname = $row->{physname
};
650 my $itempaths = &GetCurrentItemPaths
($physname, 1);
652 my $physinfo = $gPhysInfo{$physname};
654 if (!defined $physinfo) {
655 &ThrowError
("Attempt to delete unknown item '$physname':\n"
656 . $gCfg{nameResolveSeen
});
659 if ($physinfo->{parentphys
} eq $row->{parentphys
}) {
660 # Deleting from the "main" parent; find a new one by shifting off the
661 # first shared path, if any; if none exists this will leave a null
662 # parent entry. We could probably just delete the whole node at this
665 $physinfo->{parentphys
} = shift( @
{ $physinfo->{sharedphys
} } );
670 foreach my $parent (@
{ $physinfo->{sharedphys
} }) {
671 push @
$sharedphys, $parent
672 unless $parent eq $row->{parentphys
};
675 $physinfo->{sharedphys
} = $sharedphys;
680 } # End VssDeleteHandler
682 ###############################################################################
684 ###############################################################################
685 sub VssRecoverHandler
{
688 my $physname = $row->{physname
};
690 my $physinfo = $gPhysInfo{$physname};
692 if (!defined $physinfo) {
693 &ThrowError
("Attempt to recover unknown item '$physname':\n"
694 . $gCfg{nameResolveSeen
});
697 if (defined $physinfo->{parentphys
}) {
698 # Item still has other shares, so recover it by pushing this parent
699 # onto its shared list
701 push( @
{ $physinfo->{sharedphys
} }, $row->{parentphys
} );
704 # Recovering its only location; set the main parent back to this
705 $physinfo->{parentphys
} = $row->{parentphys
};
708 # We only recover the path explicitly set in this row, so build the path
709 # ourself by taking the path of this parent and appending the name
710 my $parentpaths = &GetCurrentItemPaths
($row->{parentphys
}, 1);
711 return [$parentpaths->[0] . $physinfo->{name
}];
713 } # End VssRecoverHandler
715 ###############################################################################
716 # GetCurrentItemPaths
717 ###############################################################################
718 sub GetCurrentItemPaths
{
719 my($physname, $mainonly, $recursed) = @_;
721 # Uses recursion to determine the current full paths for an item based on
722 # the name of its physical file. We can't cache this information because
723 # a rename in a parent folder would not immediately trigger a rename in
724 # all of the child items.
726 # By default, we return an anonymous array of all paths in which the item
727 # is shared, unless $mainonly is true. Luckily, only files can be shared,
728 # not projects, so once we start recursing we can set $mainonly to true.
731 $gCfg{nameResolveRecurse
} = 0;
732 $gCfg{nameResolveSeen
} = '';
733 } elsif (++$gCfg{nameResolveRecurse
} >= 1000) {
734 &ThrowError
("Infinite recursion detected while looking up parent for "
738 if ($physname eq 'AAAAAAAA') {
739 # End of recursion; all items must go back to 'AAAAAAAA', which was so
740 # named because that's what most VSS users yell after using it much. :-)
744 my $physinfo = $gPhysInfo{$physname};
746 if (!defined $physinfo) {
747 &ThrowError
("Could not determine real path for '$physname':\n"
748 . $gCfg{nameResolveSeen
});
751 $gCfg{nameResolveSeen
} .= "$physname, ";
753 my @pathstoget = $mainonly?
($physinfo->{parentphys
}) :
754 ($physinfo->{parentphys
}, @
{ $physinfo->{sharedphys
} } );
759 foreach my $parent (@pathstoget) {
760 if (!defined $parent) {
763 $result = &GetCurrentItemPaths
($parent, 1, 1);
765 push @
$paths, $result->[0] . $physinfo->{name
};
770 } # End GetCurrentItemPaths
772 ###############################################################################
774 ###############################################################################
775 sub GetCurrentItemName
{
778 my $physinfo = $gPhysInfo{$physname};
780 if (!defined $physinfo) {
781 &ThrowError
("Could not determine real name for '$physname':\n"
782 . $gCfg{nameResolveSeen
});
785 return $physinfo->{name
};
786 } # End GetCurrentItemName
788 ###############################################################################
790 ###############################################################################
792 defined($gCfg{svnurl
})?
&CheckinToSvn
: &CreateSvnDumpfile
;
795 ###############################################################################
797 ###############################################################################
802 ###############################################################################
804 ###############################################################################
805 sub CreateSvnDumpfile
{
807 } # End CreateSvnDumpfile
809 ###############################################################################
811 ###############################################################################
814 my $prefix = $gCfg{pvcsproj
} || $gCfg{svnurl
} || "log-$$";
815 $prefix =~ s
:.*[\\/]::;
816 $gCfg{logfile
} = "./logs/$prefix.txt";
817 print "All output will be logged to $gCfg{logfile}...\n";
818 open LOG
, ">>$gCfg{logfile}"
819 or die "Couldn't append to logfile $gCfg{logfile}";
820 open STDERR
, ">&LOG";
821 select STDERR
; $| = 1;
825 my $info = $gCfg{task
} eq 'INIT'?
'BEGINNING CONVERSION...' :
826 "RESUMING CONVERSION FROM TASK '$gCfg{task}' AT STEP $gCfg{step}...";
827 my $starttime = ctime
($^T
);
829 my $ssversion = &GetSsVersion
();
832 ======== VSS2SVN ========
834 Start Time : $starttime
836 VSS Dir : $gCfg{vssdir}
837 Temp Dir : $gCfg{tempdir}
839 SSPHYS exe : $gCfg{ssphys}
840 SSPHYS ver : $ssversion
846 ###############################################################################
848 ###############################################################################
851 print "\n\n\n====ERROR SUMMARY====\n\n";
856 print "\n\n\n====NO ERRORS ENCOUNTERED THIS RUN====\n\n";
859 my $starttime = ctime
($^T
);
861 my $endtime = ctime
(time);
867 my $secs = time - $^T
;
869 my $hours = $secs / 3600;
870 $secs -= ($hours * 3600);
872 my $mins = $secs / 60;
873 $secs -= ($mins * 60);
875 $elapsed = sprintf("%2.2i:%2.2i:%2.2i", $hours, $mins, $secs);
879 SVN rev range : $gCfg{firstrev} - $gCfg{lastrev}
880 Started at : $starttime
882 Elapsed time : $elapsed (H:M:S)
890 ###############################################################################
892 ###############################################################################
896 my $ok = &DoSysCmd
("\"$gCfg{ssphys}\" $cmd", 1);
898 $gSysOut =~ s/.\x08//g; # yes, I've seen VSS store backspaces in names!
899 $gSysOut =~ s/[\x00-\x09\x11\x12\x14-\x1F\x7F-\xFF]/_/g; # just to be sure
902 # ssphys.exe has bailed on us; hope we were between items and add
904 $gSysOut =~ s/^ssphys v0\.16:.*name as the source name//ms;
905 $gSysOut .= "\n</File>\n";
910 ###############################################################################
912 ###############################################################################
914 my($cmd, $allowfail) = @_;
916 print "$cmd\n" if $gCfg{verbose
};
919 print $gSysOut if $gCfg{debug
};
924 &ThrowWarning
("FAILED to execute: $!");
925 die unless $allowfail;
929 &ThrowWarning
(sprintf "FAILED with non-zero exit status %d", $?
>> 8);
930 die unless $allowfail;
939 ###############################################################################
941 ###############################################################################
943 my $out = `\"$gCfg{ssphys}\" -v 2>&1`;
944 $out =~ m/^(ssphys v.*?)[:\n]/m;
946 return $1 || 'unknown';
949 ###############################################################################
951 ###############################################################################
953 my($msg, $callinfo) = @_;
955 $callinfo ||= [caller()];
957 $msg .= "\nat $callinfo->[1] line $callinfo->[2]";
959 warn "ERROR -- $msg\n";
960 print "ERROR -- $msg\n" if $gCfg{log};
966 ###############################################################################
968 ###############################################################################
970 &ThrowWarning
(@_, [caller()]);
974 ###############################################################################
976 ###############################################################################
982 } # End StopConversion
984 ###############################################################################
986 ###############################################################################
988 my($fhname, $target) = @_;
990 (my $name = $target) =~ s/^>//;
992 print "\nOPENING FILE $name\n" if $gCfg{verbose
};
994 open $gFh{$fhname}, $target
995 or &ThrowError
("Could not open file $name");
999 ###############################################################################
1001 ###############################################################################
1005 close $gFh{$fhname};
1006 delete $gFh{$fhname};
1010 ###############################################################################
1012 ###############################################################################
1014 map { &CloseFile
($_) } values %gFh;
1016 } # End CloseAllFiles
1018 ###############################################################################
1020 ###############################################################################
1022 my($task, $leavestep) = @_;
1024 print "\nSETTING TASK $task\n" if $gCfg{verbose
};
1028 $sth = $gSth{'SYSTEMTASK'};
1030 if (!defined $sth) {
1038 $sth = $gSth{'SYSTEMTASK'} = &PrepSql
($sql);
1041 $sth->execute($task);
1043 $gCfg{task
} = $task;
1045 &SetSystemStep
(0) unless $leavestep;
1047 } # End SetSystemTask
1049 ###############################################################################
1051 ###############################################################################
1055 print "\nSETTING STEP $step\n" if $gCfg{verbose
};
1059 $sth = $gSth{'SYSTEMSTEP'};
1061 if (!defined $sth) {
1069 $sth = $gCfg{'SYSTEMSTEP'} = &PrepSql
($sql);
1072 $sth->execute($step);
1074 $gCfg{step
} = $step;
1076 } # End SetSystemStep
1078 ###############################################################################
1080 ###############################################################################
1084 my $sth = &PrepSql
("DELETE FROM $table");
1085 return $sth->execute;
1088 ###############################################################################
1090 ###############################################################################
1091 sub StartDataCache
{
1092 my($table, $delete, $autoinc) = @_;
1095 &DeleteTable
($table);
1104 $gCfg{cachetarget
} = $table;
1105 unlink $gCfg{datacache
};
1107 &OpenFile
('DATACACHE', ">$gCfg{datacache}");
1109 } # End StartDataCache
1111 ###############################################################################
1113 ###############################################################################
1117 if (ref($data[0]) eq 'ARRAY') {
1118 @data = @
{ $data[0] };
1122 unshift(@data, $gId++);
1125 my $fh = $gFh{DATACACHE
};
1126 print $fh join("\t", map {&FormatCacheData
($_)} @data), "\n";
1128 } # End AddDataCache
1130 ###############################################################################
1132 ###############################################################################
1133 sub FormatCacheData
{
1135 return '\\N' if !defined($data);
1137 $data =~ s/([\t\n\\])/\\$1/g;
1140 } # End FormatCacheData
1142 ###############################################################################
1144 ###############################################################################
1145 sub CommitDataCache
{
1148 &CloseFile
('DATACACHE');
1150 print "\n\nCOMMITTING $gCfg{cachetarget} CACHE TO DATABASE\n"
1152 $sql = "COPY $gCfg{cachetarget} FROM '$gCfg{datacache}'";
1154 $sth = &PrepSql
($sql);
1157 unlink $gCfg{datacache
};
1159 } # End CommitDataCache
1161 ###############################################################################
1163 ###############################################################################
1167 print "\nSQL:\n$sql\n" if $gCfg{debug
};
1168 return $gCfg{dbh
}->prepare($sql);
1172 ###############################################################################
1174 ###############################################################################
1175 sub ConnectDatabase
{
1176 my $db = $gCfg{sqlitedb
};
1178 if (-e
$db && (!$gCfg{resume
} ||
1179 (defined($gCfg{task
}) && $gCfg{task
} eq 'INIT'))) {
1181 unlink $db or &ThrowError
("Could not delete existing database "
1185 print "Connecting to database $db\n\n";
1187 $gCfg{dbh
} = DBI
->connect("dbi:SQLite2:dbname=$db", '', '',
1188 {RaiseError
=> 1, AutoCommit
=> 1})
1189 or die "Couldn't connect database $db: $DBI::errstr";
1191 } # End ConnectDatabase
1193 ###############################################################################
1194 # DisconnectDatabase
1195 ###############################################################################
1196 sub DisconnectDatabase
{
1197 $gCfg{dbh
}->disconnect if defined $gCfg{dbh
};
1198 } # End DisconnectDatabase
1200 ###############################################################################
1202 ###############################################################################
1204 if (defined($gCfg{task
}) && $gCfg{task
} eq 'INIT') {
1210 $gCfg{ssphys
} = 'SSPHYS.exe' if !defined($gCfg{ssphys
});
1211 $gCfg{vssdatadir
} = "$gCfg{vssdir}\\data";
1213 (-d
"$gCfg{vssdatadir}") or &ThrowError
("$gCfg{vssdir} does not appear "
1214 . "to be a valid VSS database");
1216 my($id, $type, $action);
1219 ($id, $type, $action) = split "\t";
1220 $gActionType{$id} = {type
=> $type, action
=> $action};
1223 } # End SetupGlobals
1225 ###############################################################################
1227 ###############################################################################
1238 $sth = &PrepSql
($sql);
1249 $sth = &PrepSql
($sql);
1255 action_id INTEGER PRIMARY KEY,
1271 $sth = &PrepSql
($sql);
1276 PhysicalAction_IDX1 ON PhysicalAction (
1281 $sth = &PrepSql
($sql);
1286 PhysicalAction_IDX2 ON PhysicalAction (
1295 $sth = &PrepSql
($sql);
1301 action_id INTEGER PRIMARY KEY,
1314 $sth = &PrepSql
($sql);
1326 $sth = &PrepSql
($sql);
1332 revision_id INTEGER PRIMARY KEY,
1340 $sth = &PrepSql
($sql);
1351 $sth = &PrepSql
($sql);
1354 my @cfgitems = qw(task step vssdir svnurl svnuser svnpwd ssphys tempdir
1355 setsvndate debug verbose starttime);
1357 my $fielddef = join(",\n ",
1358 map {sprintf('%-12.12s VARCHAR', $_)} @cfgitems);
1367 $sth = &PrepSql
($sql);
1370 my $fields = join(', ', @cfgitems);
1371 my $args = join(', ', map {'?'} @cfgitems);
1375 SystemInfo ($fields)
1380 $sth = &PrepSql
($sql);
1381 $sth->execute(map {$gCfg{$_}} @cfgitems);
1384 } # End InitSysTables
1386 ###############################################################################
1388 ###############################################################################
1389 sub ReloadSysTables
{
1390 my($sql, $sth, $sthup, $row, $field, $val);
1392 $sql = "SELECT * FROM SystemInfo";
1394 $sth = &PrepSql
($sql);
1397 $row = $sth->fetchrow_hashref();
1400 while (($field, $val) = each %$row) {
1401 if (defined($gCfg{$field})) { # allow user to override saved vals
1402 $sql = "UPDATE SystemInfo SET $field = ?";
1403 $sthup = &PrepSql
($sql);
1404 $sthup->execute($gCfg{$field});
1406 $gCfg{$field} = $val;
1411 &SetSystemTask
($gCfg{task
}, 1);
1413 } # End ReloadSysTables
1415 ###############################################################################
1417 ###############################################################################
1419 GetOptions
(\
%gCfg,'vssdir=s','tempdir=s','resume','verbose',
1422 &GiveHelp
("Must specify --vssdir") if !defined($gCfg{vssdir
});
1423 $gCfg{tempdir
} = '.\\_vss2svn' if !defined($gCfg{tempdir
});
1425 $gCfg{sqlitedb
} = "$gCfg{tempdir}\\vss_data.db";
1427 # XML output from ssphysout placed here.
1428 $gCfg{ssphysout
} = "$gCfg{tempdir}\\ssphysout";
1430 # SQLite data cache placed here.
1431 $gCfg{datacache
} = "$gCfg{tempdir}\\datacache.tmp.txt";
1433 # Commit messages for SVN placed here.
1434 $gCfg{svncomment
} = "$gCfg{tempdir}\\svncomment.tmp.txt";
1435 mkdir $gCfg{tempdir
} unless (-d
$gCfg{tempdir
});
1437 if ($gCfg{resume
} && !-e
$gCfg{sqlitedb
}) {
1438 warn "WARNING: --resume set but no database exists; starting new "
1443 ### Don't go past here if resuming a previous run ###
1444 if ($gCfg{resume
}) {
1448 #foreach my $check (qw(svnurl)) {
1449 # &GiveHelp("ERROR: missing required parameter $check")
1450 # unless defined $gCfg{$check};
1453 $gCfg{ssphys
} ||= 'SSPHYS.exe';
1454 $gCfg{svn
} ||= 'SVN.exe';
1456 $gCfg{task
} = 'INIT';
1458 $gCfg{starttime
} = scalar localtime($^T
);
1466 ###############################################################################
1468 ###############################################################################
1472 $msg ||= 'Online Help';
1478 USAGE: perl vss2svn.pl --vssdir <dir> [options]
1480 REQUIRED PARAMETERS:
1481 --vssdir <dir> : Directory where VSS database is located. This should be
1482 the directory in which the "srcsafe.ini" file is located.
1484 OPTIONAL PARAMETERS:
1485 --ssphys <path> : Full path to ssphys.exe program; uses PATH otherwise
1486 --tempdir <dir> : Temp directory to use during conversion;
1487 default is .\\_vss2svn
1488 --setsvndate : Set svn:date property to original VSS checkin date
1489 (see SVN:DATE WARNING in readme.txt)
1490 --log : Log all output to <tempdir>\\vss2svn.log.txt
1491 --debug : Print lots of debugging info.
1497 # Following is the data for %gActionType. First field is the node type from
1498 # ssphys; second field is item type (1=project, 2=file); third field is the
1499 # generic action it should be mapped to (loosely mapped to SVN actions)
1502 CreatedProject
1 ADD
1504 RenamedProject
1 RENAME
1505 DeletedProject
1 DELETE
1506 RecoveredProject
1 RECOVER
1510 RenamedFile
2 RENAME
1511 DeletedFile
2 DELETE
1512 RecoveredFile
2 RECOVER