3 # vss2svn.pl, Copyright (C) 2004 by Toby Johnson.
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 # http://www.gnu.org/copyleft/gpl.html
17 # ensure script can find lib directory, regardless where it's run from
19 $path =~ s
:^(.*)[/\\].*:$1: or $path = '.';
20 eval "use lib '$path/lib'";
23 die "Could not load Vss2Svn libraries: $@";
36 use Vss2Svn
::Subversion
;
42 use Win32
::TieRegistry
(Delimiter
=> '/');
44 our(%gCfg, $VSS, $SVN, $TREE, %USERS,);
47 $gCfg{allowUtf8
} = !$@
;
49 # http://www.perl.com/tchrist/defop/defconfaq.html#What_is_the_proposed_operat
51 sub PrintMsg
; # defined later
53 &Vss2Svn
::Subversion
::Initialize
;
54 &Vss2Svn
::VSS
::Initialize
;
66 &GiveHttpdAuthMessage
unless $gCfg{noprompt
};
74 PrintMsg
"\n\n**** VSS MIGRATION COMPLETED SUCCESSFULLY!! ****\n";
77 open STDERR
, ">&THE_REAL_STDERR"; # yes, we're about to exit, but leaving
78 # STDERR dangling always makes me nervous!
80 $gCfg{hooray
} = 1; # to suppress Win32::TieRegistry global destruction errors
84 ###############################################################################
86 ###############################################################################
87 sub GiveStartupMessage
{
92 if ($gCfg{setdates
}) {
97 WARNING: Commit dates can be migrated to a NEW SUBVERSION REPOSITORY only.
98 You WILL CORRUPT your data if you migrate dates to an existing repository
99 which is at any other Revision than 0!
107 ss.exe Found: $gCfg{ssbin}
108 svn.exe Found: $gCfg{svnbin}
110 VSS Project: $gCfg{vssproject}
111 Subversion URL: $gCfg{svnrepo}
113 Local Date Format: $Vss2Svn::VSS::gCfg{dateString}
114 Local Time Format: $Vss2Svn::VSS::gCfg{timeString}
115 Time Bias To Get GMT: $gCfg{timebias} minutes
117 Set SVN Commit Dates: $setdates$datemsg
120 return if $gCfg{noprompt
};
122 print "Continue with these settings? [Y/n]";
124 exit(1) if ($reply =~ m/\S/ && $reply !~ m/^y/i);
127 ###############################################################################
129 ###############################################################################
131 # redirect STDERR to logfile
132 open THE_REAL_STDERR
, ">&STDERR";
133 $gCfg{logfile
} = "$gCfg{workbase}/logfile.txt";
134 open STDERR
, ">$gCfg{logfile}"
135 or die "Couldn't open logfile $gCfg{workbase}/logfile.txt";
137 # the svn client program outputs to STDOUT; redirect to STDERR instead
138 open STDOUT
, ">&STDERR";
140 select THE_REAL_STDERR
;
144 # since we redirected STDERR, make sure user sees die() messages!
145 $SIG{__DIE__
} = \
&MyDie
;
146 $SIG{__WARN__
} = \
&PrintMsg
if $gCfg{debug
};
149 ###############################################################################
151 ###############################################################################
153 PrintMsg
"\n\n**** BUILDING INITIAL STRUCTURES; PLEASE WAIT... ****\n\n";
155 &SetStatus
(0,"Building initial structures");
157 $TREE = $VSS->project_tree($gCfg{vssproject
},1,1,1)
158 or die "Couldn't create project tree for $gCfg{vssproject}";
161 ###############################################################################
163 ###############################################################################
164 sub PruneVssExcludes
{
166 return unless defined $gCfg{vssexclude
};
168 # By this point, we already have the entire "naked" directory structure
169 # in $TREE, and we prune off any branches that match exclude. It may seem
170 # wasteful to go to the trouble of building $TREE if we're just gonna
171 # cut large chunks off now, but since we had to parse the entire output of
172 # "ss DIR" on "vssproject" anyway, we wouldn't have saved much time by
173 # using these at that stage.
175 my($ref, $parent, $subdir, $last);
178 foreach my $exclude ( sort @
{ $gCfg{vssexclude
} }) {
179 # by sorting, we get parents before their subdirectories, to give more
180 # meaningful warning messages
182 $exclude =~ s/^\s*(.*?)\s*$/$1/;
183 $exclude =~ s
:^$gCfg{vssprojmatch
}/?
::;
185 if ($exclude =~ m
:^\
$/:) {
186 PrintMsg
"**WARNING: Exclude path \"$exclude\" is not underneath "
187 . "$gCfg{vssproject}; ignoring...\n";
191 # Perl doesn't allow us to delete() a hash ref, so we must also keep
192 # track of the parent to fully get rid of the entry
193 $ref = $parent = $TREE;
195 foreach $subdir (split '\/', $exclude) {
196 if (!exists $ref->{$subdir}) {
197 PrintMsg
"**WARNING: Exclude path \"$exclude\" not found in "
198 . "$gCfg{vssproject} (or a parent directory was already "
199 . "excluded); ignoring...\n";
203 # can't use foreach() iterator outside of loop, so keep track of it
206 $ref = $ref->{$subdir};
209 delete $parent->{$last};
214 } # End PruneVssExcludes
216 ###############################################################################
218 ###############################################################################
220 chdir "$gCfg{importdir}"
221 or die "Couldn't create working directory $gCfg{importdir}";
223 PrintMsg
"\n\n**** BUILDING VSS HISTORY ****\n\n";
225 &WalkTreeBranch
($TREE, $gCfg{vssproject
});
228 ###############################################################################
230 ###############################################################################
232 my($branch, $project) = @_;
233 PrintMsg
"ENTERING PROJECT $project...\n";
235 my($key, $val, $newproj);
238 foreach $key (sort keys %$branch) {
239 $val = $branch->{$key};
241 if (ref($val) eq 'HASH') {
242 # subproject; create a new branch of the tree
244 push @branches, {branch
=> $val, project
=> "$key"};
246 } elsif (!ref $val) {
247 # a scalar, i.e. regular file
249 &AddFileHistory
($project, $key);
254 foreach my $subbranch (@branches) {
255 mkdir $subbranch->{project
};
256 chdir $subbranch->{project
}
257 or die "Could not change to working directory $subbranch->{project}";
259 ($newproj = "$project/$subbranch->{project}") =~ s
://:/:;
261 &WalkTreeBranch
($subbranch->{branch
}, $newproj);
267 ###############################################################################
269 ###############################################################################
271 my($project, $file) = @_;
273 # build the revision history for this file
275 (my $filepath = "$project/$file") =~ s
://:/:;
277 # SS.exe uses a semicolon to indicate a "pinned" file
278 $filepath =~ s/;(.*)//;
280 my $filehist = $VSS->file_history("$filepath");
281 die "Internal error while reading VSS file history for $filepath"
282 if !defined $filehist;
284 PrintMsg
" $filepath\n";
287 foreach my $rev (@
$filehist) {
288 $gCfg{globalCount
}++;
290 $rev->{user
} = lc( $rev->{user
} ); # normalize usernames to lowercase
291 $rev->{comment
} .= "\n\n$gCfg{comment}" if defined $gCfg{comment
};
293 $rev->{date
} =~ s/-//g;
294 $rev->{time} =~ s/://;
296 &InsertDatabaseRevision
($filepath, $rev);
298 $USERS{ $rev->{user
} } = 1;
303 ###############################################################################
304 # InsertDatabaseRevision
305 ###############################################################################
306 sub InsertDatabaseRevision
{
307 my($filepath, $rev) = @_;
309 my %data = %$rev; # don't pollute $rev
311 #quote the text fields
312 map { $data{$_} = $gCfg{dbh
}->quote( $rev->{$_} ) }
315 $filepath = $gCfg{dbh
}->quote($filepath);
344 or die "Could not execute DBD::SQLite command";
346 } #End InsertDatabaseRevision
348 ###############################################################################
349 # GiveHttpdAuthMessage
350 ###############################################################################
351 sub GiveHttpdAuthMessage
{
352 print THE_REAL_STDERR
<<"EOTXT";
355 Following is a list of all VSS users who have made updates at any time in the
356 specified project. In order to preserve the user history during migration to
357 Subversion, these users must exist in the Subversion authentication file.
359 Usually, this is done with an Apache "Basic" HTTP authorization file, where
360 each username is followed by a colon and the hashed password for that user.
361 A blank password is permissible. Copy and paste the following lines into this
362 authorization file in order to allow this user history to be migrated.
366 print THE_REAL_STDERR
join("\n", map {"$_:"} sort keys %USERS),
367 "\n\nPRESS ENTER TO CONTINUE (or enter [q] to quit and start over)...";
372 print THE_REAL_STDERR
"\n\nQuitting...\n";
377 ###############################################################################
379 ###############################################################################
380 sub SetupSvnProject
{
381 PrintMsg
"\n\n**** SETTING UP SUBVERSION DIRECTORIES ****\n\n";
383 &SetStatus
(1,"Setting up Subversion directories");
385 chdir $gCfg{importdir
}
386 or die "Could not change to directory $gCfg{importdir}";
388 PrintMsg
" Importing directory structure from Subversion...\n";
389 $SVN->do('import', '.', '--message "Initial Import"', 0)
390 or die "Could not perform SVN import of $gCfg{importdir}. Have you "
391 . "set your httpd authorization file correctly?";
394 or die "Could not change to directory $gCfg{workdir}";
396 PrintMsg
" Checking out working copy...\n";
397 $SVN->do('checkout', '', '"."')
398 or die "Could not perform SVN checkout of $gCfg{importdir}";
401 ###############################################################################
403 ###############################################################################
404 sub ImportSvnHistory
{
405 # we will walk the history table in date/time order, GETting from VSS
406 # as we go. VSS doesn't allow atomic multi-item commits, so we'll detect
407 # these assuming if the user and comment are the same from one item to the
408 # next, they were part of the "same" action.
410 my($row, $upd, $commitinfo);
412 my %prev = (user
=> '', comment
=> '', grain
=> 0);
413 my %all = (); # hash of all files ever added
414 my %thistime = (); # hash of files added on this commit
417 my $grain = 0.000001;
419 PrintMsg
"\n\n**** MIGRATING VSS HISTORY TO SUBVERSION ****\n\n";
421 &SetStatus
(2,"Migrating VSS history to Subversion");
423 # date, time, and file fields are formatted to enable sorting numerically
424 my $cmd = "SELECT * FROM vss2svn_history WHERE imported = 0 "
425 . "ORDER BY date, time, file";
427 my $sth = $gCfg{dbh
}->prepare($cmd)
428 or die "Could not prepare DBD::SQLite command";
430 or die "Could not execute DBD::SQLite command";
433 while ($row = $sth->fetchrow_hashref) {
434 $row->{date
} =~ s/(....)(..)(..)/$1-$2-$3/;
435 $row->{time} =~ s/(..)(..)/$1:$2/;
437 if (!exists $row->{comment
} || !defined $row->{comment
});
439 PrintMsg
" ($gCfg{commitNumber})File $row->{file}, "
440 . "$row->{date} $row->{time}...\n";
442 if (defined $prev{date
} &&
443 ($row->{date
} eq $prev{date
}) &&
444 ($row->{user
} eq $prev{user
}) &&
445 ($row->{comment
} eq $prev{comment
}) &&
446 (!defined $thistime{ $row->{file
} })) {
448 # user and comment are same; this will be multi-item commit
451 } elsif ($multiple) {
452 # we're in a multi-item commit but user or comment changed;
453 # commit previous action
455 &CommitSvn
(1, $prev{comment
}, $commitinfo);
457 &SetSvnDates
(\
%prev) if $gCfg{setdates
};
460 } elsif (defined $commitinfo) {
461 # we're not in a multi-item commit and user or comment
462 # changed; commit the single previous file
465 &CommitSvn
(0, $prev{comment
}, $commitinfo);
467 &SetSvnDates
(\
%prev) if $gCfg{setdates
};
471 if (defined $prev{date
} && ($row->{date
} ne $prev{date
})) {
474 if (defined $commitinfo) {
475 # done with this date, so commit what we have so far
476 &CommitSvn
($multiple, $prev{comment
}, $commitinfo);
479 &SetSvnDates
(\
%prev) if $gCfg{setdates
};
487 $upd = $all{ $row->{file
} }++;
488 $commitinfo = &GetVssRevision
($row, $upd, \
%thistime,);
490 %prev = (%$row, (grain
=> $grain));
495 if (defined $commitinfo) {
496 &CommitSvn
($multiple, $prev{comment
}, $commitinfo);
498 &SetSvnDates
(\
%prev) if $gCfg{setdates
};
506 ###############################################################################
508 ###############################################################################
510 my($row, $upd, $thisRef) = @_;
511 # Gets a version of a file from VSS and adds it to SVN
512 # $row is the row hash ref from the history SQLite table
513 # $upd is true if this is an update rather than add
515 my $vsspath = $row->{file
};
517 $row->{file
} =~ m/^(.*\/)(.*)/
518 or die "Mangled VSS file path information", join("\n", %$row);
519 my($path, $file) = ($1, $2);
521 $path =~ s/$gCfg{vssprojmatch}//
522 or die "Mangled VSS file path information", join("\n", %$row);
523 $path =~ s/\/$//; # remove trailing slash
525 (my $dospath = "$gCfg{workdir}/$path") =~ s
/\
//\\/g
; # use backslashes
526 $dospath =~ s/\\$//; # remove trailing backslash if $path was empty
527 $dospath =~ s/\\\\/\\/g; # replace double backslashes with single
529 my $cmd = "GET -GTM -W -GL\"$dospath\" -V$row->{version} \"$vsspath\"";
531 or die "Could not issue ss.exe command";
534 or die "Could not switch to directory $dospath";
537 $SVN->svn("add", $file)
538 or die "Could not perform SVN add of $file";
543 user
=> $row->{user
},
544 dospath
=> $dospath,};
546 $thisRef->{ $row->{file
} } = 1;
551 ###############################################################################
553 ###############################################################################
555 my($multiple, $comment, $commitinfo) = @_;
557 $comment = Encode
::encode
('utf8', $comment) if $gCfg{utf8
};
559 open COMMENTFILE
, ">$gCfg{tmpfiledir}/comment.txt"
560 or die "Could not open $gCfg{tmpfiledir}/comment.txt for writing";
561 print COMMENTFILE
$comment;
564 PrintMsg
" (COMMITTING SVN...)\n";
566 $multiple?
&CommitMultipleItems
($commitinfo)
567 : &CommitSingleItem
($commitinfo);
569 $gCfg{commitNumber
}++;
573 ###############################################################################
575 ###############################################################################
576 sub CommitSingleItem
{
577 my($commitinfo) = @_;
579 warn "SINGLE COMMIT\n";
580 chdir $commitinfo->{dospath
}
581 or die "Could not change to directory $commitinfo->{dospath}";
583 my $enc = $gCfg{utf8
}?
' --encoding UTF-8' : '';
585 $SVN->{user
} = $commitinfo->{user
};
586 $SVN->svn("commit$enc --file \"$gCfg{tmpfiledir}/comment.txt\" "
587 . "--non-recursive", $commitinfo->{file
})
588 or die "Could not perform SVN commit on \"$commitinfo->{file}\". "
589 . "Have you set your httpd authorization file correctly?";
592 ###############################################################################
593 # CommitMultipleItems
594 ###############################################################################
595 sub CommitMultipleItems
{
596 my($commitinfo) = @_;
598 warn "MULTIPLE COMMIT\n";
600 or die "Could not change to directory $gCfg{workdir}";
602 my $enc = $gCfg{utf8
}?
' --encoding UTF-8' : '';
604 $SVN->{user
} = $commitinfo->{user
};
605 $SVN->svn("commit$enc --file \"$gCfg{tmpfiledir}/comment.txt\" \".\"")
606 or die "Could not perform SVN commit. "
607 . "Have you set your httpd authorization file correctly?";
610 ###############################################################################
612 ###############################################################################
616 my $grain = sprintf '%0.6f', $info->{grain
};
617 my $svn_date = "$info->{date}T$info->{time}:${grain}Z";
619 my $cmd = "propset --revprop -rHEAD svn:date $svn_date $gCfg{svnrepo}";
621 or die "Could not perform SVN propset of $svn_date on $gCfg{svnrepo}";
625 ###############################################################################
627 ###############################################################################
628 sub RecursiveDelete
{
632 opendir(DIR
, $parent);
633 @dirs = readdir(DIR
);
636 foreach $dir (@dirs) {
637 if ($dir ne '.' && $dir ne '..') {
638 &RecursiveDelete
("$parent/$dir");
651 ###############################################################################
653 ###############################################################################
655 # print to logfile (redirected STDERR) and screen (STDOUT)
657 print THE_REAL_STDERR
@_;
660 ###############################################################################
662 ###############################################################################
664 # any die() is trapped by $SIG{__DIE__} to ensure user sees fatal errors
665 exit(255) if $gCfg{died
}; # don't die 2x if fatal error in global cleanup
666 exit(0) if $gCfg{hooray
};
669 print THE_REAL_STDERR
"\n", @_;
671 (my $logfile = $gCfg{logfile
}) =~ s
:/:\\:g
;
673 my ($vsserr, $svnerr) = ('') x
2;
675 if ((defined $VSS) && (defined $VSS->{ss_error
})) {
676 $vsserr = "\nLAST VSS COMMAND:\n$VSS->{ss_error}\n\n(You may find "
677 . "more info on this error at the following website:\n"
678 . "http://msdn.microsoft.com/library/default.asp?url=/library/"
679 . "en-us/guides/html/vsorierrormessages.asp )";
682 if ((defined $SVN) && (defined $SVN->{svn_error
})) {
683 $svnerr = "\nLAST SVN COMMAND:\n$SVN->{svn_error}\n";
686 print THE_REAL_STDERR
<<"EOERR";
688 ******************************FATAL ERROR********************************
689 *************************************************************************
691 A fatal error has occured. The output from the last VSS or SVN command is
694 See $logfile for more information.
701 ###############################################################################
703 ###############################################################################
705 GetOptions
(\
%gCfg,'vssproject=s','vssexclude=s@','svnrepo=s','comment=s',
706 'vsslogin=s','setdates','noprompt','timebias=i','restart',
707 'utf8','debug','help',);
709 &GiveHelp
(undef, 1) if defined $gCfg{help
};
711 defined $gCfg{vssproject
} or GiveHelp
("must specify --vssproject\n");
712 defined $gCfg{svnrepo
} or GiveHelp
("must specify --svnrepo\n");
713 defined $ENV{SSDIR
} or GiveHelp
("\$SSDIR not defined\n");
715 if ($gCfg{utf8
} && ! $gCfg{allowUtf8
}) {
717 ERROR: UTF-8 support is only available with the "Encoding" module, which
718 requires Perl 5.7.3 or higher. You must either install a newer version of Perl
719 or use the statically-compiled version of vss2svn to get UTF-8 support.
721 $msg = fill
('', '', $msg);
725 $gCfg{vssproject
} =~ s
:/$:: unless $gCfg{vssproject} eq '$/';
726 $gCfg{vssprojmatch} = quotemeta( $gCfg{vssproject} );
728 @{ $gCfg{vssexclude} } = split(',', join(',' ,@{ $gCfg{vssexclude} } ))
729 if defined $gCfg{vssexclude};
731 $gCfg{ssbin} = &CheckForExe
732 ("ss.exe", "the Microsoft Visual SourceSafe client");
734 $gCfg{svnbin} = &CheckForExe("svn.exe", "the Subversion client");
738 timebias => $gCfg{timebias},
741 if (defined $gCfg{vsslogin}) {
742 @{ $vss_args }{'user
', 'passwd
'} = split(':', $gCfg{vsslogin});
743 warn "\nATTENTION: about to issue VSS login command; if program\n"
744 . "hangs here, you have specified an invalid VSS username\n"
745 . "or password. (Press CTRL+Break to kill hung script)\n\n";
748 $VSS = Vss2Svn::VSS->new($ENV{SSDIR}, $gCfg{vssproject}, $vss_args);
751 $SVN = Vss2Svn::Subversion->new( $gCfg{svnrepo} );
752 $SVN->{interactive} = 0;
753 $SVN->{user} = 'vss_migration
';
754 $SVN->{passwd} = ''; # all passwords are blank
757 %USERS = ( vss_migration => 1, );
759 $gCfg{globalCount} = 1;
760 $gCfg{commitNumber} = 1;
762 $gCfg{workbase} = cwd() . "/_vss2svn";
764 print "\nCleaning up any previous vss2svn runs...\n\n";
765 &RecursiveDelete( $gCfg{workbase} );
766 mkdir $gCfg{workbase} or die "Couldn't create
$gCfg{workbase
} (does
"
767 . "another program have a
lock on this directory
or its files?
)";
769 $gCfg{workdir} = "$gCfg{workbase
}/work
";
770 mkdir $gCfg{workdir} or die "Couldn
't create $gCfg{workdir}";
772 $gCfg{importdir} = "$gCfg{workbase}/import";
773 mkdir $gCfg{importdir} or die "Couldn't create
$gCfg{importdir
}";
775 $gCfg{tmpfiledir} = "$gCfg{workbase
}/tmpfile
";
776 mkdir $gCfg{tmpfiledir} or die "Couldn
't create $gCfg{tmpfiledir}";
778 $gCfg{dbdir} = "$gCfg{workbase}/db";
779 mkdir $gCfg{dbdir} or die "Couldn't create
$gCfg{dbdir
}";
781 $VSS->{use_tempfiles} = "$gCfg{tmpfiledir
}";
785 ###############################################################################
787 ###############################################################################
789 my $bias = $Registry->{'HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/'
790 .'Control/TimeZoneInformation/ActiveTimeBias'} || 0;
792 use integer; # forces Perl to interpret two's-complement correctly
793 $gCfg{timebias} = hex($bias) + 0;
798 ###############################################################################
800 ###############################################################################
802 my($exe, $desc) = @_;
804 foreach my $dir (split ';', ".;$ENV{PATH
}") {
806 if (-f "$dir\\$exe") {
811 my $msg = fill('', '', <<"EOMSG");
812 Could not find executable '$exe' in your \%PATH\%. Ensure $desc is properly
813 installed on this computer, and manually add the directory in which '$exe' is
814 located to your path if necessary.
816 \%PATH\% currently contains:
819 die "$msg\n$ENV{PATH}\n";
822 ###############################################################################
824 ###############################################################################
826 $gCfg{dbh
} = DBI
->connect("dbi:SQLite(RaiseError=>1,AutoCommit=>0)"
827 . ":dbname=$gCfg{dbdir}/vss2svn.db","","");
831 CREATE TABLE vss2svn_history
835 file varchar(1024) NOT NULL,
836 version long NOT NULL,
837 user varchar(256) NOT NULL,
838 comment blob NOT NULL,
839 imported integer NOT NULL,
840 global_count long NOT NULL
844 $gCfg{dbh
}->do($cmd) or die;
847 CREATE TABLE vss2svn_status
850 desc varchar(1024) NOT NULL,
851 datestamp long NOT NULL
855 $gCfg{dbh
}->do($cmd) or die;
856 } #End CreateDatabase
858 ###############################################################################
860 ###############################################################################
863 $gCfg{dbh
}->disconnect;
866 ###############################################################################
868 ###############################################################################
870 my($status, $desc) = @_;
871 $desc = $gCfg{dbh
}->quote($desc);
888 $gCfg{dbh
}->do($cmd) or die;
892 ###############################################################################
894 ###############################################################################
896 my($msg, $verbose) = @_;
897 $msg .= "\n" if defined $msg;
899 $msg .= "USE --help TO VIEW FULL HELP INFORMATION\n" unless $verbose;
902 &GiveExeHelp
($msg, $verbose); # will be created by .exe build script
908 -verbose
=> $verbose,
909 -exitval
=> $verbose, # if user requested --help, go to STDOUT
919 &$code && return $_ for @_;
924 ## EXE PRECOMPILE HERE
933 vss2svn.pl, Copyright (C) 2004 by Toby Johnson.
935 This program is free software; you can redistribute it and/or
936 modify it under the terms of the GNU General Public License
937 as published by the Free Software Foundation; either version 2
938 of the License, or (at your option) any later version.
940 This program is distributed in the hope that it will be useful,
941 but WITHOUT ANY WARRANTY; without even the implied warranty of
942 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
943 GNU General Public License for more details.
944 L<http://www.gnu.org/copyleft/gpl.html>
948 vss2svn.pl S<--vssproject $/vss/project> S<--svnrepo http://svn/repo/url>
954 full path to VSS project you want to migrate
958 URL to target Subversion repository
966 =item --exclude <EXCLUDE_PROJECTS>:
968 Exclude the given projects from the migration. To list multiple projects,
969 separate with commas or use multiple --exclude commands.
971 Each project can be given as an absolute path (beginning with $/) or
972 relative to --vssproject.
974 =item --comment "MESSAGE":
976 add MESSAGE to end of every migrated comment
980 Sets the "svn:date" property off all commits to reflect the
981 original VSS commit date, so that the original commit dates
982 (and not today's date) show up in your new SVN logs. This is
983 not the default, since setting svn:date could lead to
984 problems if not done correctly. Using this also requires the
985 "pre-revprop-change" Hook Script to be set; see
986 L<http://svnbook.red-bean.com/svnbook/ch05s02.html#svn-ch-5-sect-2.1>
988 =item --vsslogin "USER:PASSWD":
990 Set VSS username and password, separated by a colon.
991 B<WARNING --> if the username/password combo you provide is
992 incorrect, this program will hang as ss.exe prompts you for
993 a username! (This is an unavoidable Microsoft bug).
995 =item --timebias <OFFSET_MINUTES>:
997 Override the script's guess as to the number of minutes it should
998 add to your local time to get to GMT (for example, if you are
999 in Eastern Daylight Time [-0400], this should be 240).
1003 Some users with non-English locales may find that the svn client
1004 causes errors when importing comments containing non-English
1005 characters. If this is the case with you, use this switch to
1006 explicitly convert all comment messages to UTF-8 before importing
1011 Don't prompt to confirm settings or to create usernames after
1016 Print all program output to screen as well as logfile.