#!/usr/bin/env perl use warnings; use strict; use lib qw(./lib); # Don't buffer standard output. Buffered stdout would be faster, but # it tends to have synchronization issues with unbuffered stderr. use IO::Handle; STDOUT->autoflush(1); use Getopt::Long; my $replayer_class = "git"; my ( $authors_file, $replay_base, $dump_file_name, $copy_depot_path, $include_regexp, $analysis_db_name, ); my $verbose = 0; my $help = 0; my $agree = 0; my $getopt_okay = GetOptions( 'analysis=s', \$analysis_db_name, 'authors=s', \$authors_file, 'copies=s', \$copy_depot_path, 'dump=s', \$dump_file_name, 'experimental', \$agree, 'help', \$help, 'include=s', \$include_regexp, 'into=s', \$replay_base, 'replayer=s', \$replayer_class, 'verbose', \$verbose, ); if ($help or !$getopt_okay) { die( "$0 usage:\n", " --replayer=CLASS how to replay the svn dump file\n", " ('git' or 'filesystem' so far)\n", " --authors=FILENAME location of git-svn authors file\n", " (optional; only for git)\n", " --dump=FILENAME location of svn dump file to replay. - for STDIN\n", " --into=PATH path where the svn dump will be replayed\n", " (must not exist)\n", " --copies=PATH directory where svn copy files are stored\n", " (must not exist)\n", " --verbose explain what's happening in great detail\n", " --help you're soaking in it.\n", " --experimental enable experimental features at one's own risk\n", " --include=REGEXP limit replay to paths matching REGEXP\n", " --analysis=FILENAME path to snanalyze sqlite3 database\n", ); } unless ( defined($replayer_class) and ($replayer_class =~ /(:?git|filesystem)/i) ) { die "$0: --replayer=git or --replayer=filesystem required\n"; } unless ($agree) { die( "$0:\n", " This is alpha-quality experimental code.\n", " Use the --experimental flag to run it at your own risk.\n", ); } unless (defined $replay_base and length $replay_base) { die "$0: --into=PATH required\n"; } if (-e $replay_base) { die "$0: --into path ($replay_base) must not exist\n"; } my $lc_replay = lc($replay_base); mkdir $lc_replay or die( "$0: cannot create temporary directory at $replay_base: $!\n" ); my $uc_exists = -e uc($replay_base); rmdir $lc_replay; if ($uc_exists) { die( "$0: case sensitive filesystem required for --into path ($replay_base)\n" ); } if (defined $authors_file) { unless (-e $authors_file) { die "$0: --authors path ($authors_file) must exist if used\n"; } unless (-f $authors_file) { die "$0: --authors path ($authors_file) must be a file\n"; } } if (defined $analysis_db_name) { unless (-e $analysis_db_name) { die "$0: --analysis path ($analysis_db_name) must exist if used\n"; } unless (-f $analysis_db_name) { die "$0: --analysis path ($analysis_db_name) must be a file\n"; } } unless (defined $dump_file_name and length $dump_file_name) { die "$0: --dump=FILENAME required\n"; } if ($dump_file_name ne '-') { unless (-e $dump_file_name) { die "$0: --dump path ($dump_file_name) doesn't exist\n"; } unless (-f $dump_file_name) { die "$0: --dump path ($dump_file_name) must be a file\n"; } } $copy_depot_path = "$replay_base.copies" unless ( defined $copy_depot_path and length $copy_depot_path ); if (-e $copy_depot_path) { die "$0: --copies path ($copy_depot_path) must not exist\n"; } # Convert the replayer class into a Perl class, and load it. $replayer_class = "SVN::Dump::Replayer::" . ucfirst(lc($replayer_class)); eval "require $replayer_class"; die if $@; $replayer_class->import(); # Begin replaying. # TODO - Coerce withiin SVN::Dump::Walker. $include_regexp = qr/$include_regexp/ if defined $include_regexp; my $replayer = $replayer_class->new( svn_dump_filename => $dump_file_name, replay_base => $replay_base, copy_source_depot => $copy_depot_path, authors_file => $authors_file, verbose => $verbose, include_regexp => $include_regexp, db_file_name => $analysis_db_name, ); $replayer->walk(); exit; __END__ =head1 NAME snerp - Export a Subversion repository to some other format =head1 SYNOPSIS snerp \ --replayer git \ --dump ./project.svndump \ --analysis ./project-index.sqlite3 \ --into ./project-git-dir \ --authors ./authors.txt \ --copies ./project-copies-dir \ --experimental =head1 DESCRIPTION snerp walks through a Subversion dump, replaying each revision into some other system. For example, the "filesystem" replayer will track the state of the files in the repository after each revisions. The filesystem copy of the repository should be essentially identical to a clean checkout, with some insignificant differences. The "git" replayer will replay each reivision into a new Git repository. The resulting Git repository should be essentially identical to the source Subversion repository, with some insignificant differences. The snerp tool works on files produced by other Snerp Vortex tools. See L for details. =head1 USAGE =head2 --analysis DB_FILENAME The location of the SQLite database corresponding to the Subversion dump being replayed. Snerp Vortex uses the database to recognize tags and branches in the source Subversion repository. Required. =head2 --authors AUTHORS_FILENAME The location of an authors.txt file used to map Subversion authors to authors in the target version control system. Modeled after git-svn's authors.txt file. Optional but recommended for Git. See "git --help svn" for the file's format and purpose. See L for a tool to find all your Subversion authors. =head2 --copies TEMPORARY_COPIES_DIRECTORY A temporary directory where copied files are kept until needed. Not all replayers can support copies that come from earlier revisions, so snerp caches the source files in the TEMPORARY_COPIES_DIRECTORY until it's time to actually copy them. This directory may become quite large, espeically in large projects that branch a lot. =head2 --dump SVN_DUMP_FILENAME The location of the Subversion dump to replay. Required since nothing can be done without one. =head2 --experimental By using the --experimental flag, you acknowlede that Snerp Vortex is experimental software that you wish to use at your own risk. Very much required. The snerp tool does potentially harmful things like deleting entire directories. We hope it's safe, but the things Snerp Vortex does makes it a good candidate for abuse. If it accidentally wipes out your machine, please file an actionable bug. You are otherwise on your own. =head2 --include REGEXP A regular expression to limit the directories included in the replayed repository. Experimental, and be aware that the option can break copy operations that cross the border between included and excluded directories. It's probably better to convert the entire repository, then clean up the structure in the new VCS. Optional and discouraged. =head2 --into NEW_BASE_DIRECTORY The base directory into which the new repository will be replayed. Required. =head2 --replayer REPLAYER_CLASS The type of repository to create. By default only "filesystem" and "git" are allowed, but we hope people write plugins for their favorite version control systems. Required. =head2 --verbose Enable more debugging output than your screen has room for. Traces every move snerp makes. Every step it takes. =head1 TODO Remove temporary copy sources from the --copies directory when they're not longer needed. =head1 SEE ALSO L - Main documentation for Snerp Vortex. L - Subversion dumps are parsed by SVN::Dump. snanalyze - Analyze a Subversion dump, and produce an index database for other tools to process. snassign-auto - Automatically assign tags and branches to a snanalyze index. snassign-gui - Graphical snanalyze index browser. Future plans will allow users to assign branches and tags by hand. Requires Gtk. snauthors - Extract a basic authors.txt file from a Subversion dump. =head1 AUTHORS AND LICENSE Snerp Vortex is Copyright 2010 by Rocco Caputo and contributors. It is released under the same terms as Perl itself. =cut