All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Eric S. Raymond" <esr@thyrsus.com>
To: git@vger.kernel.org
Subject: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.
Date: Tue, 1 Jan 2013 12:26:45 -0500	[thread overview]
Message-ID: <20130101172645.GA5506@thyrsus.com> (raw)

The combination of git-cvsimport and cvsps had serious problems.
Among these were:

(1) Analysis of branchy repos was buggy in multiple ways in both
    programs, leading to incorrect repo translations.

(2) Even after a correct branch analysis, extra (redundant) fileops
    would often be generated on the new-branch side.

(3) Inability to report more than one tag pointing to the same revision.

(4) Failure in certain cases of clock-skew reported by the t9603 test.

(5) Failure to use commitids for changeset ordering in cases were this
    would have prevented clock skew from causing incorrect grouping.

Problems 2-5 and portions of problem 1 have been solved by a major
rewrite of cvsps (the 3.x release series); it now emits a git
fast-import stream.  Also, the buggy attempt at ancestry-branch
tracking previously invoked by -A has been replaced with a simpler and
better topo analysis.  cvsps is now about 20% smaller than formerly.

All this changed cvsps's interface enough to require a complete
rewrite of git-cvsimport (hence this patch). In the process the code
size of the wrapper script dropped by about x3 and it can now support
alternate conversion engines; the first new engine is cvs2git, with
parsecvs expected to follow shortly if that code proves salvageable.

This patch also removes Michael Haggerty's git-cvsimport tests from
the git tree.  These are actually conversion-engine tests and have been
merged into a larger cvsps test suite, which I intend to spin out into
a general CVS-lifting test that can also be applied to utilities such
as cvs2git and parsecvs.

The following known bug has not been fixed: "If any files were ever "cvs
import"ed more than once (e.g., import of more than one vendor
release) the HEAD contains the wrong content." However, cvsps now
emits a warning in this case. There is also one pathological tagging
case that was successful the former t9602 test that now fails (with
a warning).

I plan to address these problems. This patch at least gets the
cvsps-3.x/git-cvsimport combination to a state that is not too
broken to ship - that is, in all failure cases known to me it
now emits useful warnings rather than silently botching the
import.
---
 Documentation/git-cvsimport.txt                    |  303 +++--
 Makefile                                           |    2 +-
 git-cvsimport.perl                                 | 1177 --------------------
 git-cvsimport.py                                   |  342 ++++++
 t/t9601-cvsimport-vendor-branch.sh                 |   85 --
 t/t9601/cvsroot/.gitattributes                     |    1 -
 t/t9601/cvsroot/CVSROOT/.gitignore                 |    2 -
 t/t9601/cvsroot/module/added-imported.txt,v        |   44 -
 t/t9601/cvsroot/module/imported-anonymously.txt,v  |   42 -
 .../module/imported-modified-imported.txt,v        |   76 --
 t/t9601/cvsroot/module/imported-modified.txt,v     |   59 -
 t/t9601/cvsroot/module/imported-once.txt,v         |   43 -
 t/t9601/cvsroot/module/imported-twice.txt,v        |   60 -
 t/t9602-cvsimport-branches-tags.sh                 |   78 --
 t/t9602/README                                     |   62 --
 t/t9602/cvsroot/.gitattributes                     |    1 -
 t/t9602/cvsroot/CVSROOT/.gitignore                 |    2 -
 t/t9602/cvsroot/module/default,v                   |  102 --
 t/t9602/cvsroot/module/sub1/default,v              |  102 --
 t/t9602/cvsroot/module/sub1/subsubA/default,v      |  101 --
 t/t9602/cvsroot/module/sub1/subsubB/default,v      |  107 --
 .../module/sub2/Attic/branch_B_MIXED_only,v        |   59 -
 t/t9602/cvsroot/module/sub2/default,v              |  102 --
 t/t9602/cvsroot/module/sub2/subsubA/default,v      |  102 --
 t/t9602/cvsroot/module/sub3/default,v              |  102 --
 t/t9603-cvsimport-patchsets.sh                     |   39 -
 t/t9603/cvsroot/.gitattributes                     |    1 -
 t/t9603/cvsroot/CVSROOT/.gitignore                 |    2 -
 t/t9603/cvsroot/module/a,v                         |   74 --
 t/t9603/cvsroot/module/b,v                         |   90 --
 30 files changed, 492 insertions(+), 2870 deletions(-)
 delete mode 100755 git-cvsimport.perl
 create mode 100755 git-cvsimport.py
 delete mode 100755 t/t9601-cvsimport-vendor-branch.sh
 delete mode 100644 t/t9601/cvsroot/.gitattributes
 delete mode 100644 t/t9601/cvsroot/CVSROOT/.gitignore
 delete mode 100644 t/t9601/cvsroot/module/added-imported.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-anonymously.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-modified-imported.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-modified.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-once.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-twice.txt,v
 delete mode 100755 t/t9602-cvsimport-branches-tags.sh
 delete mode 100644 t/t9602/README
 delete mode 100644 t/t9602/cvsroot/.gitattributes
 delete mode 100644 t/t9602/cvsroot/CVSROOT/.gitignore
 delete mode 100644 t/t9602/cvsroot/module/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub1/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub1/subsubA/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub1/subsubB/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v
 delete mode 100644 t/t9602/cvsroot/module/sub2/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub2/subsubA/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub3/default,v
 delete mode 100755 t/t9603-cvsimport-patchsets.sh
 delete mode 100644 t/t9603/cvsroot/.gitattributes
 delete mode 100644 t/t9603/cvsroot/CVSROOT/.gitignore
 delete mode 100644 t/t9603/cvsroot/module/a,v
 delete mode 100644 t/t9603/cvsroot/module/b,v

diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt
index 98d9881..92da652 100644
--- a/Documentation/git-cvsimport.txt
+++ b/Documentation/git-cvsimport.txt
@@ -9,75 +9,78 @@ git-cvsimport - Salvage your data out of another SCM people love to hate
 SYNOPSIS
 --------
 [verse]
-'git cvsimport' [-o <branch-for-HEAD>] [-h] [-v] [-d <CVSROOT>]
-	      [-A <author-conv-file>] [-p <options-for-cvsps>] [-P <file>]
-	      [-C <git_repository>] [-z <fuzz>] [-i] [-k] [-u] [-s <subst>]
-	      [-a] [-m] [-M <regex>] [-S <regex>] [-L <commitlimit>]
-	      [-r <remote>] [-R] [<CVS_module>]
-
+'git cvsimport' [-A <author-conv-file>] [-b] [-C <git_repository>] 
+              [-d <CVSROOT>] [-h] [-i] [-k] [-p <options-for-engine>] 
+	      [-P <cvsps-output-file>] [-r <remote>] [-R] [-s <subst>] 
+	      [-S <regex>] [-u] [-v] [-z <fuzz>] [<CVS_module>]
 
 DESCRIPTION
 -----------
-Imports a CVS repository into git. It will either create a new
+Imports a CVS repository into git. This tool will either create a new
 repository, or incrementally import into an existing one.
 
-Splitting the CVS log into patch sets is done by 'cvsps'.
-At least version 2.1 is required.
-
-*WARNING:* for certain situations the import leads to incorrect results.
-Please see the section <<issues,ISSUES>> for further reference.
-
-You should *never* do any work of your own on the branches that are
-created by 'git cvsimport'.  By default initial import will create and populate a
-"master" branch from the CVS repository's main branch which you're free
-to work with; after that, you need to 'git merge' incremental imports, or
-any CVS branches, yourself.  It is advisable to specify a named remote via
--r to separate and protect the incoming branches.
-
-If you intend to set up a shared public repository that all developers can
-read/write, or if you want to use linkgit:git-cvsserver[1], then you
-probably want to make a bare clone of the imported repository,
-and use the clone as the shared repository.
-See linkgit:gitcvs-migration[7].
+*WARNING:* The CVS model of version control lends itself to all manner
+of perversities; not all sequences of CVS operations can be translated
+into an import stream, and importing is not guaranteed to produce a
+perfectly accurate representation of CVS history. Please see the
+section on <<issues,engine-specific issues>> for further reference.
 
+git cvsimport will do well at translating CVS repositories with a
+linear or close-to-linear revision history, no merges, and
+well-disciplined tagging practices.  More complex cases will require
+human judgment amplified by a repository-editing tool such as
+http://www.catb.org/~esr/reposurgeon[reposurgeon].
 
 OPTIONS
 -------
--v::
-	Verbosity: let 'cvsimport' report what it is doing.
-
--d <CVSROOT>::
-	The root of the CVS archive. May be local (a simple path) or remote;
-	currently, only the :local:, :ext: and :pserver: access methods
-	are supported. If not given, 'git cvsimport' will try to read it
-	from `CVS/Root`. If no such file exists, it checks for the
-	`CVSROOT` environment variable.
+-A <author-conv-file>::
+	CVS by default uses the Unix username when writing its
+	commit logs. Using this option and an author-conv-file
+	maps the name recorded in CVS to author name, e-mail and
+	optional timezone:
++
+---------
+	exon=Andreas Ericsson <ae@op5.se> +0200
+	spawn=Simon Pawn <spawn@frog-pond.org> -0500
+---------
++
+'git cvsimport' will make it appear as those authors had
+their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly
+all along.
++
+For convenience, this data is saved to `$GIT_DIR/cvs-authors`
+each time the '-A' option is provided and read from that same
+file each time 'git cvsimport' is run.
++
+It is not recommended to use this feature if you intend to
+export changes back to CVS again later with
+'git cvsexportcommit'.
 
-<CVS_module>::
-	The CVS module you want to import. Relative to <CVSROOT>.
-	If not given, 'git cvsimport' tries to read it from
-	`CVS/Repository`.
+-b::
+        Create a bare repo. If you intend to set up a shared public
+	repository that all developers can read/write, or if you want
+	to use linkgit:git-cvsserver[1], then you probably want to
+	make a bare clone of the imported repository using this
+	option. See linkgit:gitcvs-migration[7].
 
 -C <target-dir>::
         The git repository to import to.  If the directory doesn't
         exist, it will be created.  Default is the current directory.
 
--r <remote>::
-	The git remote to import this CVS repository into.
-	Moves all CVS branches into remotes/<remote>/<branch>
-	akin to the way 'git clone' uses 'origin' by default.
+-d <CVSROOT>::
+	The root of the CVS archive. It is only necessary to specify
+	this option if you are running from somewhere other than a 
+	CVS checkout directory; the value is passed to the conversion
+	engine to be interpreted.
 
--o <branch-for-HEAD>::
-	When no remote is specified (via -r) the 'HEAD' branch
-	from CVS is imported to the 'origin' branch within the git
-	repository, as 'HEAD' already has a special meaning for git.
-	When a remote is specified the 'HEAD' branch is named
-	remotes/<remote>/master mirroring 'git clone' behaviour.
-	Use this option if you want to import into a different
-	branch.
-+
-Use '-o master' for continuing an import that was initially done by
-the old cvs2git tool.
+-e <engine>::
+	Splitting the CVS log into patch sets is done by an engine program,
+	which must emit a git fast-import stream to standard output.
+	This option changes the engine used; when given, it must be the
+	first option on the command line.
+
+-h::
+	Print a short usage message and exit.
 
 -i::
 	Import-only: don't perform a checkout after importing.  This option
@@ -89,75 +92,19 @@ the old cvs2git tool.
 	to avoid noisy changesets. Highly recommended, but off by default
 	to preserve compatibility with early imported trees.
 
--u::
-	Convert underscores in tag and branch names to dots.
-
--s <subst>::
-	Substitute the character "/" in branch names with <subst>
-
--p <options-for-cvsps>::
-	Additional options for cvsps.
-	The options '-u' and '-A' are implicit and should not be used here.
-+
-If you need to pass multiple options, separate them with a comma.
-
--z <fuzz>::
-	Pass the timestamp fuzz factor to cvsps, in seconds. If unset,
-	cvsps defaults to 300s.
-
 -P <cvsps-output-file>::
-	Instead of calling cvsps, read the provided cvsps output file. Useful
-	for debugging or when cvsps is being handled outside cvsimport.
-
--m::
-	Attempt to detect merges based on the commit message. This option
-	will enable default regexes that try to capture the source
-	branch name from the commit message.
-
--M <regex>::
-	Attempt to detect merges based on the commit message with a custom
-	regex. It can be used with '-m' to enable the default regexes
-	as well. You must escape forward slashes.
-+
-The regex must capture the source branch name in $1.
-+
-This option can be used several times to provide several detection regexes.
-
--S <regex>::
-	Skip paths matching the regex.
+	Instead of calling a conversion engine, read the provided
+	import-stream file. Useful for debugging or when the first
+	stage of conversion is being handled outside cvsimport.
 
--a::
-	Import all commits, including recent ones. cvsimport by default
-	skips commits that have a timestamp less than 10 minutes ago.
-
--L <limit>::
-	Limit the number of commits imported. Workaround for cases where
-	cvsimport leaks memory.
-
--A <author-conv-file>::
-	CVS by default uses the Unix username when writing its
-	commit logs. Using this option and an author-conv-file
-	maps the name recorded in CVS to author name, e-mail and
-	optional timezone:
-+
----------
-	exon=Andreas Ericsson <ae@op5.se>
-	spawn=Simon Pawn <spawn@frog-pond.org> America/Chicago
+-r <remote>::
+	The git remote to import this CVS repository into.
+	Moves all CVS branches into remotes/<remote>/<branch>
+	akin to the way 'git clone' uses 'origin' by default.
 
----------
-+
-'git cvsimport' will make it appear as those authors had
-their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly
-all along.  If a timezone is specified, GIT_AUTHOR_DATE will
-have the corresponding offset applied.
-+
-For convenience, this data is saved to `$GIT_DIR/cvs-authors`
-each time the '-A' option is provided and read from that same
-file each time 'git cvsimport' is run.
-+
-It is not recommended to use this feature if you intend to
-export changes back to CVS again later with
-'git cvsexportcommit'.
+-p <options-for-engine>::
+	Additional options for the engine. If you need to pass
+	multiple options, separate them with a comma.
 
 -R::
 	Generate a `$GIT_DIR/cvs-revisions` file containing a mapping from CVS
@@ -174,50 +121,98 @@ doing incremental imports.
 +
 This option may be useful if you have CVS revision numbers stored in commit
 messages, bug-tracking systems, email archives, and the like.
++
+-s <subst>::
+	Substitute the character "/" in branch names with <subst>
 
--h::
-	Print a short usage message and exit.
-
-OUTPUT
-------
-If '-v' is specified, the script reports what it is doing.
-
-Otherwise, success is indicated the Unix way, i.e. by simply exiting with
-a zero exit status.
+-S <regex>::
+	Skip paths matching the regex.
 
-[[issues]]
-ISSUES
-------
-Problems related to timestamps:
+-u::
+	Convert underscores in tag and branch names to dots.
 
- * If timestamps of commits in the CVS repository are not stable enough
-   to be used for ordering commits changes may show up in the wrong
-   order.
- * If any files were ever "cvs import"ed more than once (e.g., import of
-   more than one vendor release) the HEAD contains the wrong content.
- * If the timestamp order of different files cross the revision order
-   within the commit matching time window the order of commits may be
-   wrong.
+-v::
+	Verbosity: let 'cvsimport' report what it is doing.
 
-Problems related to branches:
+-z <fuzz>::
+	Pass the timestamp fuzz factor, in seconds. If unset, this has 
+	an engine-dependent default - usually 300s.
 
- * Branches on which no commits have been made are not imported.
- * All files from the branching point are added to a branch even if
-   never added in CVS.
- * This applies to files added to the source branch *after* a daughter
-   branch was created: if previously no commit was made on the daughter
-   branch they will erroneously be added to the daughter branch in git.
+<CVS_module>::
+	The CVS module you want to import. Relative to <CVSROOT>.  It
+	is only necessary to specify this option if you are running
+	from somewhere other than a CVS checkout directory; the value
+	is passed to the conversion engine to be interpreted.
 
-Problems related to tags:
+OUTPUT
+------
+If '-v' is specified, the program reports what it is doing.
 
-* Multiple tags on the same revision are not imported.
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
 
-If you suspect that any of these issues may apply to the repository you
-want to import consider using these alternative tools which proved to be
-more stable in practice:
+[[compatibility]]
+COMPATIBILITY
+-------------
+In 2012 two serious bugs dating back to 2006 in cvsps were exposed.  The
+ancestry-branch tracking formerly enabled by -A did not work, and
+branch detection was generally buggy; translations of branchy repos
+could be mangled.  While the --fast-export mode in 3.x releases of
+cvsps solved the problem, it required an emergency rewrite of
+git-cvsimport.  Some compatibility with older versions was unavoidably
+lost
+
+The -a, -o, -m, -M, and -L options in older versions of this tool have
+been removed (in effect, -a is always on; you can negate it with
+suitably crafted -d arguments).  Certain older versions could take a
+named timezone (like "America/Chicago") in an author-map file rather
+than just a [+-]hhmm offset; this version doesn't do that, but the
+capability may be restored in a future release.
 
-* cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org`
-* parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs`
+[[issues]]
+ENGINE-SPECIFIC ISSUES
+----------------------
+The conversion engines try to warn you about repository histories they
+can't handle; see their individual manual pages to learn how to
+interpret the warnings you may receive.
+
+The default conversion engine is 'cvsps'.  If warnings you receive
+suggest that the repository translation is invalid, consider switching
+engines to 'cvs2git'.
+
+cvsps
+~~~~~
+The default conversion engine is 'cvsps'; at least version 3.3 is
+required.  The cvsps project page is at `http://www.catb.org/~esr/cvsps`.
+Things to know about this engine:
+
+* As well as working from within a CVS checkout directory,
+  'cvsps' will also adapt without requiring a root or module
+  specification when run from within a module subdirectory within a CVS
+  repository directory.  When run from within the top level of a CVS
+  repository, 'cvsps' requires only a module argument.
+
+* The -S option will interpret exclusion regular expressions using the
+  POSIX syntax of regex(7).
+
+* 'cvsps' automatically removes characters in CVS tag and branch names
+  that would be illegal in git.
+
+cvs2git
+~~~~~~~
+The cvs2git project page is at `http://cvs2svn.tigris.org`.  It is
+much slower than cvsps, and does not implement some git-cvsimport
+options (such as -d and -A), but it handles a wider range of
+pathological CVS cases.
+
+* 'cvs2git' takes a path option pointing to a repository module
+  subdirectory, defaulting to ".".
+
+* The -S option will interpret exclusion regular expressions using
+  Python syntax.
+
+* Illegal characters in branch and tag names will cause cvs2git to
+  abort with an error message.
 
 GIT
 ---
diff --git a/Makefile b/Makefile
index 736ecd4..626494e 100644
--- a/Makefile
+++ b/Makefile
@@ -464,7 +464,6 @@ SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
-SCRIPT_PERL += git-cvsimport.perl
 SCRIPT_PERL += git-cvsserver.perl
 SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
@@ -472,6 +471,7 @@ SCRIPT_PERL += git-svn.perl
 
 SCRIPT_PYTHON += git-remote-testgit.py
 SCRIPT_PYTHON += git-p4.py
+SCRIPT_PYTHON += git-cvsimport.py
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
 	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
deleted file mode 100755
index 0a31ebd..0000000
--- a/git-cvsimport.perl
+++ /dev/null
@@ -1,1177 +0,0 @@
-#!/usr/bin/perl
-
-# This tool is copyright (c) 2005, Matthias Urlichs.
-# It is released under the Gnu Public License, version 2.
-#
-# The basic idea is to aggregate CVS check-ins into related changes.
-# Fortunately, "cvsps" does that for us; all we have to do is to parse
-# its output.
-#
-# Checking out the files is done by a single long-running CVS connection
-# / server process.
-#
-# The head revision is on branch "origin" by default.
-# You can change that with the '-o' option.
-
-use 5.008;
-use strict;
-use warnings;
-use Getopt::Long;
-use File::Spec;
-use File::Temp qw(tempfile tmpnam);
-use File::Path qw(mkpath);
-use File::Basename qw(basename dirname);
-use Time::Local;
-use IO::Socket;
-use IO::Pipe;
-use POSIX qw(strftime tzset dup2 ENOENT);
-use IPC::Open2;
-
-$SIG{'PIPE'}="IGNORE";
-set_timezone('UTC');
-
-our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R);
-my (%conv_author_name, %conv_author_email, %conv_author_tz);
-
-sub usage(;$) {
-	my $msg = shift;
-	print(STDERR "Error: $msg\n") if $msg;
-	print STDERR <<END;
-Usage: git cvsimport     # fetch/update GIT from CVS
-       [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
-       [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
-       [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
-       [-r remote] [-R] [CVS_module]
-END
-	exit(1);
-}
-
-sub read_author_info($) {
-	my ($file) = @_;
-	my $user;
-	open my $f, '<', "$file" or die("Failed to open $file: $!\n");
-
-	while (<$f>) {
-		# Expected format is this:
-		#   exon=Andreas Ericsson <ae@op5.se>
-		if (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/) {
-			$user = $1;
-			$conv_author_name{$user} = $2;
-			$conv_author_email{$user} = $3;
-		}
-		# or with an optional timezone:
-		#   spawn=Simon Pawn <spawn@frog-pond.org> America/Chicago
-		elsif (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*(\S+?)\s*$/) {
-			$user = $1;
-			$conv_author_name{$user} = $2;
-			$conv_author_email{$user} = $3;
-			$conv_author_tz{$user} = $4;
-		}
-		# However, we also read from CVSROOT/users format
-		# to ease migration.
-		elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) {
-			my $mapped;
-			($user, $mapped) = ($1, $3);
-			if ($mapped =~ /^\s*(.*?)\s*<(.*)>\s*$/) {
-				$conv_author_name{$user} = $1;
-				$conv_author_email{$user} = $2;
-			}
-			elsif ($mapped =~ /^<?(.*)>?$/) {
-				$conv_author_name{$user} = $user;
-				$conv_author_email{$user} = $1;
-			}
-		}
-		# NEEDSWORK: Maybe warn on unrecognized lines?
-	}
-	close ($f);
-}
-
-sub write_author_info($) {
-	my ($file) = @_;
-	open my $f, '>', $file or
-	  die("Failed to open $file for writing: $!");
-
-	foreach (keys %conv_author_name) {
-		print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>";
-		print $f " $conv_author_tz{$_}" if ($conv_author_tz{$_});
-		print $f "\n";
-	}
-	close ($f);
-}
-
-# Versions of perl before 5.10.0 may not automatically check $TZ each
-# time localtime is run (most platforms will do so only the first time).
-# We can work around this by using tzset() to update the internal
-# variable whenever we change the environment.
-sub set_timezone {
-	$ENV{TZ} = shift;
-	tzset();
-}
-
-# convert getopts specs for use by git config
-my %longmap = (
-	'A:' => 'authors-file',
-	'M:' => 'merge-regex',
-	'P:' => undef,
-	'R' => 'track-revisions',
-	'S:' => 'ignore-paths',
-);
-
-sub read_repo_config {
-	# Split the string between characters, unless there is a ':'
-	# So "abc:de" becomes ["a", "b", "c:", "d", "e"]
-	my @opts = split(/ *(?!:)/, shift);
-	foreach my $o (@opts) {
-		my $key = $o;
-		$key =~ s/://g;
-		my $arg = 'git config';
-		$arg .= ' --bool' if ($o !~ /:$/);
-		my $ckey = $key;
-
-		if (exists $longmap{$o}) {
-			# An uppercase option like -R cannot be
-			# expressed in the configuration, as the
-			# variable names are downcased.
-			$ckey = $longmap{$o};
-			next if (! defined $ckey);
-			$ckey =~ s/-//g;
-		}
-		chomp(my $tmp = `$arg --get cvsimport.$ckey`);
-		if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
-			no strict 'refs';
-			my $opt_name = "opt_" . $key;
-			if (!$$opt_name) {
-				$$opt_name = $tmp;
-			}
-		}
-	}
-}
-
-my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R";
-read_repo_config($opts);
-Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
-
-# turn the Getopt::Std specification in a Getopt::Long one,
-# with support for multiple -M options
-GetOptions( map { s/:/=s/; /M/ ? "$_\@" : $_ } split( /(?!:)/, $opts ) )
-    or usage();
-usage if $opt_h;
-
-if (@ARGV == 0) {
-		chomp(my $module = `git config --get cvsimport.module`);
-		push(@ARGV, $module) if $? == 0;
-}
-@ARGV <= 1 or usage("You can't specify more than one CVS module");
-
-if ($opt_d) {
-	$ENV{"CVSROOT"} = $opt_d;
-} elsif (-f 'CVS/Root') {
-	open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
-	$opt_d = <$f>;
-	chomp $opt_d;
-	close $f;
-	$ENV{"CVSROOT"} = $opt_d;
-} elsif ($ENV{"CVSROOT"}) {
-	$opt_d = $ENV{"CVSROOT"};
-} else {
-	usage("CVSROOT needs to be set");
-}
-$opt_s ||= "-";
-$opt_a ||= 0;
-
-my $git_tree = $opt_C;
-$git_tree ||= ".";
-
-my $remote;
-if (defined $opt_r) {
-	$remote = 'refs/remotes/' . $opt_r;
-	$opt_o ||= "master";
-} else {
-	$opt_o ||= "origin";
-	$remote = 'refs/heads';
-}
-
-my $cvs_tree;
-if ($#ARGV == 0) {
-	$cvs_tree = $ARGV[0];
-} elsif (-f 'CVS/Repository') {
-	open my $f, '<', 'CVS/Repository' or
-	    die 'Failed to open CVS/Repository';
-	$cvs_tree = <$f>;
-	chomp $cvs_tree;
-	close $f;
-} else {
-	usage("CVS module has to be specified");
-}
-
-our @mergerx = ();
-if ($opt_m) {
-	@mergerx = ( qr/\b(?:from|of|merge|merging|merged) ([-\w]+)/i );
-}
-if (@opt_M) {
-	push (@mergerx, map { qr/$_/ } @opt_M);
-}
-
-# Remember UTC of our starting time
-# we'll want to avoid importing commits
-# that are too recent
-our $starttime = time();
-
-select(STDERR); $|=1; select(STDOUT);
-
-
-package CVSconn;
-# Basic CVS dialog.
-# We're only interested in connecting and downloading, so ...
-
-use File::Spec;
-use File::Temp qw(tempfile);
-use POSIX qw(strftime dup2);
-
-sub new {
-	my ($what,$repo,$subdir) = @_;
-	$what=ref($what) if ref($what);
-
-	my $self = {};
-	$self->{'buffer'} = "";
-	bless($self,$what);
-
-	$repo =~ s#/+$##;
-	$self->{'fullrep'} = $repo;
-	$self->conn();
-
-	$self->{'subdir'} = $subdir;
-	$self->{'lines'} = undef;
-
-	return $self;
-}
-
-sub find_password_entry {
-	my ($cvspass, @cvsroot) = @_;
-	my ($file, $delim) = @$cvspass;
-	my $pass;
-	local ($_);
-
-	if (open(my $fh, $file)) {
-		# :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-		CVSPASSFILE:
-		while (<$fh>) {
-			chomp;
-			s/^\/\d+\s+//;
-			my ($w, $p) = split($delim,$_,2);
-			for my $cvsroot (@cvsroot) {
-				if ($w eq $cvsroot) {
-					$pass = $p;
-					last CVSPASSFILE;
-				}
-			}
-		}
-		close($fh);
-	}
-	return $pass;
-}
-
-sub conn {
-	my $self = shift;
-	my $repo = $self->{'fullrep'};
-	if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
-		my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
-
-		my ($proxyhost,$proxyport);
-		if ($param && ($param =~ m/proxy=([^;]+)/)) {
-			$proxyhost = $1;
-			# Default proxyport, if not specified, is 8080.
-			$proxyport = 8080;
-			if ($ENV{"CVS_PROXY_PORT"}) {
-				$proxyport = $ENV{"CVS_PROXY_PORT"};
-			}
-			if ($param =~ m/proxyport=([^;]+)/) {
-				$proxyport = $1;
-			}
-		}
-		$repo ||= '/';
-
-		# if username is not explicit in CVSROOT, then use current user, as cvs would
-		$user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || "anonymous") unless $user;
-		my $rr2 = "-";
-		unless ($port) {
-			$rr2 = ":pserver:$user\@$serv:$repo";
-			$port=2401;
-		}
-		my $rr = ":pserver:$user\@$serv:$port$repo";
-
-		if ($pass) {
-			$pass = $self->_scramble($pass);
-		} else {
-			my @cvspass = ([$ENV{'HOME'}."/.cvspass", qr/\s/],
-				       [$ENV{'HOME'}."/.cvs/cvspass", qr/=/]);
-			my @loc = ();
-			foreach my $cvspass (@cvspass) {
-				my $p = find_password_entry($cvspass, $rr, $rr2);
-				if ($p) {
-					push @loc, $cvspass->[0];
-					$pass = $p;
-				}
-			}
-
-			if (1 < @loc) {
-				die("Multiple cvs password files have ".
-				    "entries for CVSROOT $opt_d: @loc");
-			} elsif (!$pass) {
-				$pass = "A";
-			}
-		}
-
-		my ($s, $rep);
-		if ($proxyhost) {
-
-			# Use a HTTP Proxy. Only works for HTTP proxies that
-			# don't require user authentication
-			#
-			# See: http://www.ietf.org/rfc/rfc2817.txt
-
-			$s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
-			die "Socket to $proxyhost: $!\n" unless defined $s;
-			$s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
-	                        or die "Write to $proxyhost: $!\n";
-	                $s->flush();
-
-			$rep = <$s>;
-
-			# The answer should look like 'HTTP/1.x 2yy ....'
-			if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
-				die "Proxy connect: $rep\n";
-			}
-			# Skip up to the empty line of the proxy server output
-			# including the response headers.
-			while ($rep = <$s>) {
-				last if (!defined $rep ||
-					 $rep eq "\n" ||
-					 $rep eq "\r\n");
-			}
-		} else {
-			$s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
-			die "Socket to $serv: $!\n" unless defined $s;
-		}
-
-		$s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
-			or die "Write to $serv: $!\n";
-		$s->flush();
-
-		$rep = <$s>;
-
-		if ($rep ne "I LOVE YOU\n") {
-			$rep="<unknown>" unless $rep;
-			die "AuthReply: $rep\n";
-		}
-		$self->{'socketo'} = $s;
-		$self->{'socketi'} = $s;
-	} else { # local or ext: Fork off our own cvs server.
-		my $pr = IO::Pipe->new();
-		my $pw = IO::Pipe->new();
-		my $pid = fork();
-		die "Fork: $!\n" unless defined $pid;
-		my $cvs = 'cvs';
-		$cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};
-		my $rsh = 'rsh';
-		$rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};
-
-		my @cvs = ($cvs, 'server');
-		my ($local, $user, $host);
-		$local = $repo =~ s/:local://;
-		if (!$local) {
-		    $repo =~ s/:ext://;
-		    $local = !($repo =~ s/^(?:([^\@:]+)\@)?([^:]+)://);
-		    ($user, $host) = ($1, $2);
-		}
-		if (!$local) {
-		    if ($user) {
-			unshift @cvs, $rsh, '-l', $user, $host;
-		    } else {
-			unshift @cvs, $rsh, $host;
-		    }
-		}
-
-		unless ($pid) {
-			$pr->writer();
-			$pw->reader();
-			dup2($pw->fileno(),0);
-			dup2($pr->fileno(),1);
-			$pr->close();
-			$pw->close();
-			exec(@cvs);
-		}
-		$pw->writer();
-		$pr->reader();
-		$self->{'socketo'} = $pw;
-		$self->{'socketi'} = $pr;
-	}
-	$self->{'socketo'}->write("Root $repo\n");
-
-	# Trial and error says that this probably is the minimum set
-	$self->{'socketo'}->write("Valid-responses ok error Valid-requests Mode M Mbinary E Checked-in Created Updated Merged Removed\n");
-
-	$self->{'socketo'}->write("valid-requests\n");
-	$self->{'socketo'}->flush();
-
-	my $rep=$self->readline();
-	die "Failed to read from server" unless defined $rep;
-	chomp($rep);
-	if ($rep !~ s/^Valid-requests\s*//) {
-		$rep="<unknown>" unless $rep;
-		die "Expected Valid-requests from server, but got: $rep\n";
-	}
-	chomp(my $res=$self->readline());
-	die "validReply: $res\n" if $res ne "ok";
-
-	$self->{'socketo'}->write("UseUnchanged\n") if $rep =~ /\bUseUnchanged\b/;
-	$self->{'repo'} = $repo;
-}
-
-sub readline {
-	my ($self) = @_;
-	return $self->{'socketi'}->getline();
-}
-
-sub _file {
-	# Request a file with a given revision.
-	# Trial and error says this is a good way to do it. :-/
-	my ($self,$fn,$rev) = @_;
-	$self->{'socketo'}->write("Argument -N\n") or return undef;
-	$self->{'socketo'}->write("Argument -P\n") or return undef;
-	# -kk: Linus' version doesn't use it - defaults to off
-	if ($opt_k) {
-	    $self->{'socketo'}->write("Argument -kk\n") or return undef;
-	}
-	$self->{'socketo'}->write("Argument -r\n") or return undef;
-	$self->{'socketo'}->write("Argument $rev\n") or return undef;
-	$self->{'socketo'}->write("Argument --\n") or return undef;
-	$self->{'socketo'}->write("Argument $self->{'subdir'}/$fn\n") or return undef;
-	$self->{'socketo'}->write("Directory .\n") or return undef;
-	$self->{'socketo'}->write("$self->{'repo'}\n") or return undef;
-	# $self->{'socketo'}->write("Sticky T1.0\n") or return undef;
-	$self->{'socketo'}->write("co\n") or return undef;
-	$self->{'socketo'}->flush() or return undef;
-	$self->{'lines'} = 0;
-	return 1;
-}
-sub _line {
-	# Read a line from the server.
-	# ... except that 'line' may be an entire file. ;-)
-	my ($self, $fh) = @_;
-	die "Not in lines" unless defined $self->{'lines'};
-
-	my $line;
-	my $res=0;
-	while (defined($line = $self->readline())) {
-		# M U gnupg-cvs-rep/AUTHORS
-		# Updated gnupg-cvs-rep/
-		# /daten/src/rsync/gnupg-cvs-rep/AUTHORS
-		# /AUTHORS/1.1///T1.1
-		# u=rw,g=rw,o=rw
-		# 0
-		# ok
-
-		if ($line =~ s/^(?:Created|Updated) //) {
-			$line = $self->readline(); # path
-			$line = $self->readline(); # Entries line
-			my $mode = $self->readline(); chomp $mode;
-			$self->{'mode'} = $mode;
-			defined (my $cnt = $self->readline())
-				or die "EOF from server after 'Changed'\n";
-			chomp $cnt;
-			die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
-			$line="";
-			$res = $self->_fetchfile($fh, $cnt);
-		} elsif ($line =~ s/^ //) {
-			print $fh $line;
-			$res += length($line);
-		} elsif ($line =~ /^M\b/) {
-			# output, do nothing
-		} elsif ($line =~ /^Mbinary\b/) {
-			my $cnt;
-			die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
-			chomp $cnt;
-			die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
-			$line="";
-			$res += $self->_fetchfile($fh, $cnt);
-		} else {
-			chomp $line;
-			if ($line eq "ok") {
-				# print STDERR "S: ok (".length($res).")\n";
-				return $res;
-			} elsif ($line =~ s/^E //) {
-				# print STDERR "S: $line\n";
-			} elsif ($line =~ /^(Remove-entry|Removed) /i) {
-				$line = $self->readline(); # filename
-				$line = $self->readline(); # OK
-				chomp $line;
-				die "Unknown: $line" if $line ne "ok";
-				return -1;
-			} else {
-				die "Unknown: $line\n";
-			}
-		}
-	}
-	return undef;
-}
-sub file {
-	my ($self,$fn,$rev) = @_;
-	my $res;
-
-	my ($fh, $name) = tempfile('gitcvs.XXXXXX',
-		    DIR => File::Spec->tmpdir(), UNLINK => 1);
-
-	$self->_file($fn,$rev) and $res = $self->_line($fh);
-
-	if (!defined $res) {
-	    print STDERR "Server has gone away while fetching $fn $rev, retrying...\n";
-	    truncate $fh, 0;
-	    $self->conn();
-	    $self->_file($fn,$rev) or die "No file command send";
-	    $res = $self->_line($fh);
-	    die "Retry failed" unless defined $res;
-	}
-	close ($fh);
-
-	return ($name, $res);
-}
-sub _fetchfile {
-	my ($self, $fh, $cnt) = @_;
-	my $res = 0;
-	my $bufsize = 1024 * 1024;
-	while ($cnt) {
-	    if ($bufsize > $cnt) {
-		$bufsize = $cnt;
-	    }
-	    my $buf;
-	    my $num = $self->{'socketi'}->read($buf,$bufsize);
-	    die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
-	    print $fh $buf;
-	    $res += $num;
-	    $cnt -= $num;
-	}
-	return $res;
-}
-
-sub _scramble {
-	my ($self, $pass) = @_;
-	my $scrambled = "A";
-
-	return $scrambled unless $pass;
-
-	my $pass_len = length($pass);
-	my @pass_arr = split("", $pass);
-	my $i;
-
-	# from cvs/src/scramble.c
-	my @shifts = (
-		  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
-		 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
-		114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
-		111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
-		 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
-		125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
-		 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
-		 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
-		225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
-		199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
-		174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
-		207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
-		192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
-		227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
-		182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
-		243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
-	);
-
-	for ($i = 0; $i < $pass_len; $i++) {
-		$scrambled .= pack("C", $shifts[ord($pass_arr[$i])]);
-	}
-
-	return $scrambled;
-}
-
-package main;
-
-my $cvs = CVSconn->new($opt_d, $cvs_tree);
-
-
-sub pdate($) {
-	my ($d) = @_;
-	m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
-		or die "Unparseable date: $d\n";
-	my $y=$1; $y-=1900 if $y>1900;
-	return timegm($6||0,$5,$4,$3,$2-1,$y);
-}
-
-sub pmode($) {
-	my ($mode) = @_;
-	my $m = 0;
-	my $mm = 0;
-	my $um = 0;
-	for my $x(split(//,$mode)) {
-		if ($x eq ",") {
-			$m |= $mm&$um;
-			$mm = 0;
-			$um = 0;
-		} elsif ($x eq "u") { $um |= 0700;
-		} elsif ($x eq "g") { $um |= 0070;
-		} elsif ($x eq "o") { $um |= 0007;
-		} elsif ($x eq "r") { $mm |= 0444;
-		} elsif ($x eq "w") { $mm |= 0222;
-		} elsif ($x eq "x") { $mm |= 0111;
-		} elsif ($x eq "=") { # do nothing
-		} else { die "Unknown mode: $mode\n";
-		}
-	}
-	$m |= $mm&$um;
-	return $m;
-}
-
-sub getwd() {
-	my $pwd = `pwd`;
-	chomp $pwd;
-	return $pwd;
-}
-
-sub is_sha1 {
-	my $s = shift;
-	return $s =~ /^[a-f0-9]{40}$/;
-}
-
-sub get_headref ($) {
-	my $name = shift;
-	my $r = `git rev-parse --verify '$name' 2>/dev/null`;
-	return undef unless $? == 0;
-	chomp $r;
-	return $r;
-}
-
-my $user_filename_prepend = '';
-sub munge_user_filename {
-	my $name = shift;
-	return File::Spec->file_name_is_absolute($name) ?
-		$name :
-		$user_filename_prepend . $name;
-}
-
--d $git_tree
-	or mkdir($git_tree,0777)
-	or die "Could not create $git_tree: $!";
-if ($git_tree ne '.') {
-	$user_filename_prepend = getwd() . '/';
-	chdir($git_tree);
-}
-
-my $last_branch = "";
-my $orig_branch = "";
-my %branch_date;
-my $tip_at_start = undef;
-
-my $git_dir = $ENV{"GIT_DIR"} || ".git";
-$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
-$ENV{"GIT_DIR"} = $git_dir;
-my $orig_git_index;
-$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
-
-my %index; # holds filenames of one index per branch
-
-unless (-d $git_dir) {
-	system(qw(git init));
-	die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-	system(qw(git read-tree --empty));
-	die "Cannot init an empty tree: $?\n" if $?;
-
-	$last_branch = $opt_o;
-	$orig_branch = "";
-} else {
-	open(F, "-|", qw(git symbolic-ref HEAD)) or
-		die "Cannot run git symbolic-ref: $!\n";
-	chomp ($last_branch = <F>);
-	$last_branch = basename($last_branch);
-	close(F);
-	unless ($last_branch) {
-		warn "Cannot read the last branch name: $! -- assuming 'master'\n";
-		$last_branch = "master";
-	}
-	$orig_branch = $last_branch;
-	$tip_at_start = `git rev-parse --verify HEAD`;
-
-	# Get the last import timestamps
-	my $fmt = '($ref, $author) = (%(refname), %(author));';
-	my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
-	open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
-	while (defined(my $entry = <H>)) {
-		my ($ref, $author);
-		eval($entry) || die "cannot eval refs list: $@";
-		my ($head) = ($ref =~ m|^$remote/(.*)|);
-		$author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
-		$branch_date{$head} = $1;
-	}
-	close(H);
-        if (!exists $branch_date{$opt_o}) {
-		die "Branch '$opt_o' does not exist.\n".
-		       "Either use the correct '-o branch' option,\n".
-		       "or import to a new repository.\n";
-        }
-}
-
--d $git_dir
-	or die "Could not create git subdir ($git_dir).\n";
-
-# now we read (and possibly save) author-info as well
--f "$git_dir/cvs-authors" and
-  read_author_info("$git_dir/cvs-authors");
-if ($opt_A) {
-	read_author_info(munge_user_filename($opt_A));
-	write_author_info("$git_dir/cvs-authors");
-}
-
-# open .git/cvs-revisions, if requested
-open my $revision_map, '>>', "$git_dir/cvs-revisions"
-    or die "Can't open $git_dir/cvs-revisions for appending: $!\n"
-	if defined $opt_R;
-
-
-#
-# run cvsps into a file unless we are getting
-# it passed as a file via $opt_P
-#
-my $cvspsfile;
-unless ($opt_P) {
-	print "Running cvsps...\n" if $opt_v;
-	my $pid = open(CVSPS,"-|");
-	my $cvspsfh;
-	die "Cannot fork: $!\n" unless defined $pid;
-	unless ($pid) {
-		my @opt;
-		@opt = split(/,/,$opt_p) if defined $opt_p;
-		unshift @opt, '-z', $opt_z if defined $opt_z;
-		unshift @opt, '-q'         unless defined $opt_v;
-		unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
-			push @opt, '--cvs-direct';
-		}
-		exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
-		die "Could not start cvsps: $!\n";
-	}
-	($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
-					  DIR => File::Spec->tmpdir());
-	while (<CVSPS>) {
-	    print $cvspsfh $_;
-	}
-	close CVSPS;
-	$? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
-	close $cvspsfh;
-} else {
-	$cvspsfile = munge_user_filename($opt_P);
-}
-
-open(CVS, "<$cvspsfile") or die $!;
-
-## cvsps output:
-#---------------------
-#PatchSet 314
-#Date: 1999/09/18 13:03:59
-#Author: wkoch
-#Branch: STABLE-BRANCH-1-0
-#Ancestor branch: HEAD
-#Tag: (none)
-#Log:
-#    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch
-#Members:
-#	README:1.57->1.57.2.1
-#	VERSION:1.96->1.96.2.1
-#
-#---------------------
-
-my $state = 0;
-
-sub update_index (\@\@) {
-	my $old = shift;
-	my $new = shift;
-	open(my $fh, '|-', qw(git update-index -z --index-info))
-		or die "unable to open git update-index: $!";
-	print $fh
-		(map { "0 0000000000000000000000000000000000000000\t$_\0" }
-			@$old),
-		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
-			@$new)
-		or die "unable to write to git update-index: $!";
-	close $fh
-		or die "unable to write to git update-index: $!";
-	$? and die "git update-index reported error: $?";
-}
-
-sub write_tree () {
-	open(my $fh, '-|', qw(git write-tree))
-		or die "unable to open git write-tree: $!";
-	chomp(my $tree = <$fh>);
-	is_sha1($tree)
-		or die "Cannot get tree id ($tree): $!";
-	close($fh)
-		or die "Error running git write-tree: $?\n";
-	print "Tree ID $tree\n" if $opt_v;
-	return $tree;
-}
-
-my ($patchset,$date,$author_name,$author_email,$author_tz,$branch,$ancestor,$tag,$logmsg);
-my (@old,@new,@skipped,%ignorebranch,@commit_revisions);
-
-# commits that cvsps cannot place anywhere...
-$ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
-
-sub commit {
-	if ($branch eq $opt_o && !$index{branch} &&
-		!get_headref("$remote/$branch")) {
-	    # looks like an initial commit
-	    # use the index primed by git init
-	    $ENV{GIT_INDEX_FILE} = "$git_dir/index";
-	    $index{$branch} = "$git_dir/index";
-	} else {
-	    # use an index per branch to speed up
-	    # imports of projects with many branches
-	    unless ($index{$branch}) {
-		$index{$branch} = tmpnam();
-		$ENV{GIT_INDEX_FILE} = $index{$branch};
-		if ($ancestor) {
-		    system("git", "read-tree", "$remote/$ancestor");
-		} else {
-		    system("git", "read-tree", "$remote/$branch");
-		}
-		die "read-tree failed: $?\n" if $?;
-	    }
-	}
-        $ENV{GIT_INDEX_FILE} = $index{$branch};
-
-	update_index(@old, @new);
-	@old = @new = ();
-	my $tree = write_tree();
-	my $parent = get_headref("$remote/$last_branch");
-	print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
-
-	my @commit_args;
-	push @commit_args, ("-p", $parent) if $parent;
-
-	# loose detection of merges
-	# based on the commit msg
-	foreach my $rx (@mergerx) {
-		next unless $logmsg =~ $rx && $1;
-		my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
-		if (my $sha1 = get_headref("$remote/$mparent")) {
-			push @commit_args, '-p', "$remote/$mparent";
-			print "Merge parent branch: $mparent\n" if $opt_v;
-		}
-	}
-
-	set_timezone($author_tz);
-	my $commit_date = strftime("%s %z", localtime($date));
-	set_timezone('UTC');
-	$ENV{GIT_AUTHOR_NAME} = $author_name;
-	$ENV{GIT_AUTHOR_EMAIL} = $author_email;
-	$ENV{GIT_AUTHOR_DATE} = $commit_date;
-	$ENV{GIT_COMMITTER_NAME} = $author_name;
-	$ENV{GIT_COMMITTER_EMAIL} = $author_email;
-	$ENV{GIT_COMMITTER_DATE} = $commit_date;
-	my $pid = open2(my $commit_read, my $commit_write,
-		'git', 'commit-tree', $tree, @commit_args);
-
-	# compatibility with git2cvs
-	substr($logmsg,32767) = "" if length($logmsg) > 32767;
-	$logmsg =~ s/[\s\n]+\z//;
-
-	if (@skipped) {
-	    $logmsg .= "\n\n\nSKIPPED:\n\t";
-	    $logmsg .= join("\n\t", @skipped) . "\n";
-	    @skipped = ();
-	}
-
-	print($commit_write "$logmsg\n") && close($commit_write)
-		or die "Error writing to git commit-tree: $!\n";
-
-	print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
-	chomp(my $cid = <$commit_read>);
-	is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
-	print "Commit ID $cid\n" if $opt_v;
-	close($commit_read);
-
-	waitpid($pid,0);
-	die "Error running git commit-tree: $?\n" if $?;
-
-	system('git' , 'update-ref', "$remote/$branch", $cid) == 0
-		or die "Cannot write branch $branch for update: $!\n";
-
-	if ($revision_map) {
-		print $revision_map "@$_ $cid\n" for @commit_revisions;
-	}
-	@commit_revisions = ();
-
-	if ($tag) {
-	        my ($xtag) = $tag;
-		$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
-		$xtag =~ tr/_/\./ if ( $opt_u );
-		$xtag =~ s/[\/]/$opt_s/g;
-
-		# See refs.c for these rules.
-		# Tag cannot contain bad chars. (See bad_ref_char in refs.c.)
-		$xtag =~ s/[ ~\^:\\\*\?\[]//g;
-		# Other bad strings for tags:
-		# (See check_refname_component in refs.c.)
-		1 while $xtag =~ s/
-			(?: \.\.        # Tag cannot contain '..'.
-			|   \@{         # Tag cannot contain '@{'.
-			| ^ -           # Tag cannot begin with '-'.
-			|   \.lock $    # Tag cannot end with '.lock'.
-			| ^ \.          # Tag cannot begin...
-			|   \. $        # ...or end with '.'
-			)//xg;
-		# Tag cannot be empty.
-		if ($xtag eq '') {
-			warn("warning: ignoring tag '$tag'",
-			" with invalid tagname\n");
-			return;
-		}
-
-		if (system('git' , 'tag', '-f', $xtag, $cid) != 0) {
-			# We did our best to sanitize the tag, but still failed
-			# for whatever reason. Bail out, and give the user
-			# enough information to understand if/how we should
-			# improve the translation in the future.
-			if ($tag ne $xtag) {
-				print "Translated '$tag' tag to '$xtag'\n";
-			}
-			die "Cannot create tag $xtag: $!\n";
-		}
-
-		print "Created tag '$xtag' on '$branch'\n" if $opt_v;
-	}
-};
-
-my $commitcount = 1;
-while (<CVS>) {
-	chomp;
-	if ($state == 0 and /^-+$/) {
-		$state = 1;
-	} elsif ($state == 0) {
-		$state = 1;
-		redo;
-	} elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
-		$patchset = 0+$_;
-		$state=2;
-	} elsif ($state == 2 and s/^Date:\s+//) {
-		$date = pdate($_);
-		unless ($date) {
-			print STDERR "Could not parse date: $_\n";
-			$state=0;
-			next;
-		}
-		$state=3;
-	} elsif ($state == 3 and s/^Author:\s+//) {
-		$author_tz = "UTC";
-		s/\s+$//;
-		if (/^(.*?)\s+<(.*)>/) {
-		    ($author_name, $author_email) = ($1, $2);
-		} elsif ($conv_author_name{$_}) {
-			$author_name = $conv_author_name{$_};
-			$author_email = $conv_author_email{$_};
-			$author_tz = $conv_author_tz{$_} if ($conv_author_tz{$_});
-		} else {
-		    $author_name = $author_email = $_;
-		}
-		$state = 4;
-	} elsif ($state == 4 and s/^Branch:\s+//) {
-		s/\s+$//;
-		tr/_/\./ if ( $opt_u );
-		s/[\/]/$opt_s/g;
-		$branch = $_;
-		$state = 5;
-	} elsif ($state == 5 and s/^Ancestor branch:\s+//) {
-		s/\s+$//;
-		$ancestor = $_;
-		$ancestor = $opt_o if $ancestor eq "HEAD";
-		$state = 6;
-	} elsif ($state == 5) {
-		$ancestor = undef;
-		$state = 6;
-		redo;
-	} elsif ($state == 6 and s/^Tag:\s+//) {
-		s/\s+$//;
-		if ($_ eq "(none)") {
-			$tag = undef;
-		} else {
-			$tag = $_;
-		}
-		$state = 7;
-	} elsif ($state == 7 and /^Log:/) {
-		$logmsg = "";
-		$state = 8;
-	} elsif ($state == 8 and /^Members:/) {
-		$branch = $opt_o if $branch eq "HEAD";
-		if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
-			# skip
-			print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
-			$state = 11;
-			next;
-		}
-		if (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {
-			# skip if the commit is too recent
-			# given that the cvsps default fuzz is 300s, we give ourselves another
-			# 300s just in case -- this also prevents skipping commits
-			# due to server clock drift
-			print "skip patchset $patchset: $date too recent\n" if $opt_v;
-			$state = 11;
-			next;
-		}
-		if (exists $ignorebranch{$branch}) {
-			print STDERR "Skipping $branch\n";
-			$state = 11;
-			next;
-		}
-		if ($ancestor) {
-			if ($ancestor eq $branch) {
-				print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
-				$ancestor = $opt_o;
-			}
-			if (defined get_headref("$remote/$branch")) {
-				print STDERR "Branch $branch already exists!\n";
-				$state=11;
-				next;
-			}
-			my $id = get_headref("$remote/$ancestor");
-			if (!$id) {
-				print STDERR "Branch $ancestor does not exist!\n";
-				$ignorebranch{$branch} = 1;
-				$state=11;
-				next;
-			}
-
-			system(qw(git update-ref -m cvsimport),
-				"$remote/$branch", $id);
-			if($? != 0) {
-				print STDERR "Could not create branch $branch\n";
-				$ignorebranch{$branch} = 1;
-				$state=11;
-				next;
-			}
-		}
-		$last_branch = $branch if $branch ne $last_branch;
-		$state = 9;
-	} elsif ($state == 8) {
-		$logmsg .= "$_\n";
-	} elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
-#	VERSION:1.96->1.96.2.1
-		my $init = ($2 eq "INITIAL");
-		my $fn = $1;
-		my $rev = $3;
-		$fn =~ s#^/+##;
-		if ($opt_S && $fn =~ m/$opt_S/) {
-		    print "SKIPPING $fn v $rev\n";
-		    push(@skipped, $fn);
-		    next;
-		}
-		push @commit_revisions, [$fn, $rev];
-		print "Fetching $fn   v $rev\n" if $opt_v;
-		my ($tmpname, $size) = $cvs->file($fn,$rev);
-		if ($size == -1) {
-			push(@old,$fn);
-			print "Drop $fn\n" if $opt_v;
-		} else {
-			print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
-			my $pid = open(my $F, '-|');
-			die $! unless defined $pid;
-			if (!$pid) {
-			    exec("git", "hash-object", "-w", $tmpname)
-				or die "Cannot create object: $!\n";
-			}
-			my $sha = <$F>;
-			chomp $sha;
-			close $F;
-			my $mode = pmode($cvs->{'mode'});
-			push(@new,[$mode, $sha, $fn]); # may be resurrected!
-		}
-		unlink($tmpname);
-	} elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
-		my $fn = $1;
-		my $rev = $2;
-		$fn =~ s#^/+##;
-		push @commit_revisions, [$fn, $rev];
-		push(@old,$fn);
-		print "Delete $fn\n" if $opt_v;
-	} elsif ($state == 9 and /^\s*$/) {
-		$state = 10;
-	} elsif (($state == 9 or $state == 10) and /^-+$/) {
-		$commitcount++;
-		if ($opt_L && $commitcount > $opt_L) {
-			last;
-		}
-		commit();
-		if (($commitcount & 1023) == 0) {
-			system(qw(git repack -a -d));
-		}
-		$state = 1;
-	} elsif ($state == 11 and /^-+$/) {
-		$state = 1;
-	} elsif (/^-+$/) { # end of unknown-line processing
-		$state = 1;
-	} elsif ($state != 11) { # ignore stuff when skipping
-		print STDERR "* UNKNOWN LINE * $_\n";
-	}
-}
-commit() if $branch and $state != 11;
-
-unless ($opt_P) {
-	unlink($cvspsfile);
-}
-
-# The heuristic of repacking every 1024 commits can leave a
-# lot of unpacked data.  If there is more than 1MB worth of
-# not-packed objects, repack once more.
-my $line = `git count-objects`;
-if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
-  my ($n_objects, $kb) = ($1, $2);
-  1024 < $kb
-    and system(qw(git repack -a -d));
-}
-
-foreach my $git_index (values %index) {
-    if ($git_index ne "$git_dir/index") {
-	unlink($git_index);
-    }
-}
-
-if (defined $orig_git_index) {
-	$ENV{GIT_INDEX_FILE} = $orig_git_index;
-} else {
-	delete $ENV{GIT_INDEX_FILE};
-}
-
-# Now switch back to the branch we were in before all of this happened
-if ($orig_branch) {
-	print "DONE.\n" if $opt_v;
-	if ($opt_i) {
-		exit 0;
-	}
-	my $tip_at_end = `git rev-parse --verify HEAD`;
-	if ($tip_at_start ne $tip_at_end) {
-		for ($tip_at_start, $tip_at_end) { chomp; }
-		print "Fetched into the current branch.\n" if $opt_v;
-		system(qw(git read-tree -u -m),
-		       $tip_at_start, $tip_at_end);
-		die "Fast-forward update failed: $?\n" if $?;
-	}
-	else {
-		system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
-		die "Could not merge $opt_o into the current branch.\n" if $?;
-	}
-} else {
-	$orig_branch = "master";
-	print "DONE; creating $orig_branch branch\n" if $opt_v;
-	system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
-		unless defined get_headref('refs/heads/master');
-	system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
-		if ($opt_r && $opt_o ne 'HEAD');
-	system('git', 'update-ref', 'HEAD', "$orig_branch");
-	unless ($opt_i) {
-		system(qw(git checkout -f));
-		die "checkout failed: $?\n" if $?;
-	}
-}
diff --git a/git-cvsimport.py b/git-cvsimport.py
new file mode 100755
index 0000000..6407e8a
--- /dev/null
+++ b/git-cvsimport.py
@@ -0,0 +1,342 @@
+#!/usr/bin/env python
+#
+# Import CVS history into git
+#
+# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.
+#
+# By Eric S. Raymond <esr@thyrsus.com>, December 2012
+# May be redistributed under the license of the git project.
+
+import sys
+
+if sys.hexversion < 0x02060000:
+    sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+    sys.exit(1)
+
+import os, getopt, subprocess, tempfile, shutil
+
+DEBUG_COMMANDS = 1
+
+class Fatal(Exception):
+    "Unrecoverable error."
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def do_or_die(dcmd, legend=""):
+    "Either execute a command or raise a fatal exception."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        retcode = subprocess.call(dcmd, shell=True)
+        if retcode < 0:
+            raise Fatal("git cvsimport: child was terminated by signal %d." % -retcode)
+        elif retcode != 0:
+            raise Fatal("git cvsimport: child returned %d." % retcode)
+    except (OSError, IOError) as e:
+        raise Fatal("git cvsimport: execution of %s%s failed: %s" % (dcmd, legend, e))
+
+def capture_or_die(dcmd, legend=""):
+    "Either execute a command and capture its output or die."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        return subprocess.check_output(dcmd, shell=True)
+    except subprocess.CalledProcessError as e:
+        if e.returncode < 0:
+            sys.stderr.write("git cvsimport: child was terminated by signal %d." % -e.returncode)
+        elif e.returncode != 0:
+            sys.stderr.write("git cvsimport: child returned %d." % e.returncode)
+        sys.exit(1)
+    
+class cvsps:
+    "Method class for cvsps back end."
+    def __init__(self):
+        self.opts = ""
+        self.revmap = None
+    def set_repo(self, val):
+        "Set the repository root option."
+        if not val.startswith(":"):
+            if not val.startswith(os.sep):
+                val = os.path.abspath(val)
+            val = ":local:" + val
+        self.opts += " --root '%s'" % val
+    def set_authormap(self, val):
+        "Set the author-map file."
+        self.opts += " -A '%s'" % val
+    def set_fuzz(self, val):
+        "Set the commit-similarity window."
+        self.opts += " -z %s" % val
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " -k"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " -n -f '%s'" % val
+    def set_after(self, val):
+        "Set a date threshold for incremental import."
+        self.opts += " -d '%s'" % val
+    def set_revmap(self, val):
+        "Set the file to which the engine should dump a reference map."
+        self.revmap = val
+        self.opts += " -R '%s'" % self.revmap
+    def set_module(self, val):
+        "Set the module to query."
+        self.opts += " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "cvsps --fast-export " + self.opts
+
+class cvs2git:
+    "Method class for cvs2git back end."
+    def __init__(self):
+        self.opts = ""
+        self.modulepath = "."
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author maping is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        sys.stderr.write("git cvsimport: cvs2git must run within a repository checkout directory.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        sys.stderr.write("git cvsimport: fuzz setting is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " --keywords-off"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " --exclude='%s'" % val
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        sys.stderr.write("git cvsimport: incremental import is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, val):
+        "Set the module to query."
+        self.modulepath = " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "(cvs2git --username=git-cvsimport --quiet --quiet --blobfile={0} --dumpfile={1} {2} {3} && cat {0} {1} && rm {0} {1})".format(tempfile.mkstemp()[1], tempfile.mkstemp()[1], self.opts, self.modulepath)
+
+class filesource:
+    "Method class for file-source back end."
+    def __init__(self, filename):
+        self.filename = filename
+    def __complain(self, legend):
+        sys.stderr.write("git cvsimport: %s with file source.\n" % legend)
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        self.__complain("repository can't be set")
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author maping is not supported with filesource.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        self.__complain("fuzz can't be set")
+    def set_nokeywords(self, _val):
+        "Suppress CVS keyword expansion."
+        self.__complain("keyword suppression can't be set")
+    def add_opts(self, _val):
+        "Add options to the engine command line."
+        self.__complain("other options can't be set")
+    def set_exclusion(self, _val):
+        "Set a file exclusion regexp."
+        self.__complain("exclusions can't be set")
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        pass
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, _val):
+        "Set the module to query."
+        self.__complain("module can't be set")
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "cat " + self.filename
+
+if __name__ == '__main__':
+    if sys.hexversion < 0x02060000:
+        sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+        sys.exit(1)
+    (options, arguments) = getopt.getopt(sys.argv[1:], "vbe:d:C:r:o:ikus:p:z:P:S:aL:A:Rh")
+    verbose = 0
+    bare = False
+    root = None
+    outdir = os.getcwd()
+    remotize = False
+    import_only = False
+    underscore_to_dot = False
+    slashsubst = None
+    authormap = None
+    revisionmap = False
+    backend = cvsps()
+    for (opt, val) in options:
+        if opt == '-v':
+            verbose += 1
+        elif opt == '-b':
+            bare = True
+        elif opt == '-e':
+            for cls in (cvsps, cvs2git):
+                if cls.__name__ == val:
+                    backend = cls()
+                    break
+            else:
+                sys.stderr.write("git cvsimport: unknown engine %s.\n" % val)
+                sys.exit(1)
+        elif opt == '-d':
+            backend.set_repo(val)
+        elif opt == '-C':
+            outdir = val
+        elif opt == '-r':
+            remotize = True
+        elif opt == '-o':
+            sys.stderr.write("git cvsimport: -o is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-i':
+            import_only = True
+        elif opt == '-k':
+            backend.set_nokeywords()
+        elif opt == '-u':
+            underscore_to_dot = True
+        elif opt == '-s':
+            slashsubst = val
+        elif opt == '-p':
+            backend.add_opts(val.replace(",", " "))
+        elif opt == '-z':
+            backend.set_fuzz(val)
+        elif opt == '-P':
+            backend = filesource(val)
+            sys.exit(1)
+        elif opt in ('-m', '-M'):
+            sys.stderr.write("git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\n")
+            sys.exit(1)
+        elif opt == '-S':
+            backend.set_exclusion(val)
+        elif opt == '-a':
+            sys.stderr.write("git cvsimport: -a is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-L':
+            sys.stderr.write("git cvsimport: -L is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-A':
+            authormap = os.path.abspath(val)
+        elif opt == '-R':
+            revisionmap = True
+        else:
+            print """\
+git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>]
+     [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>]
+     [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>]
+     [<CVS_module>]
+"""         
+
+    def metadata(fn):
+        if bare:
+            return fn
+        else:
+            return os.path.join(".git", fn) 
+    try:
+        if outdir:
+            try:
+                # If the output directory does not exist, create it
+                # and initialize it as a git repository.
+                os.mkdir(outdir)
+                do_or_die("git init --quiet " + outdir)
+            except OSError:
+                # Otherwise, assume user wants incremental import.
+                if not bare and not os.path.exists(os.path.join(outdir, ".git")):
+                    raise Fatal("output directory is not a git repository")
+                threshold = capture_or_die("git log -1 --format=%ct").strip()
+                backend.set_after(threshold)
+        if revisionmap:
+            backend.set_revmap(tempfile.mkstemp()[1])
+            markmap = tempfile.mkstemp()[1]
+        if arguments:
+            backend.set_module(arguments[0])
+        gitopts = ""
+        if bare:
+            gitopts += " --bare"
+        if revisionmap:
+            gitopts += " --export-marks='%s'" % markmap
+        if authormap:
+            shutil.copyfile(authormap, metadata("cvs_authors"))
+        if os.path.exists(metadata("cvs-authors")):
+            backend.set_authormap(metadata("cvs-authors"))
+        do_or_die("%s | (cd %s >/dev/null; git fast-import --quiet %s)" \
+                  % (backend.command(), outdir, gitopts))
+        os.chdir(outdir)
+        if underscore_to_dot or slashsubst:
+            tagnames = capture_or_die("git tag -l")
+            for tag in tagnames.split():
+                if tag:
+                    changed = tag
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if changed != tag:
+                        do_or_die("git tag -f %s %s >/dev/null" % (tag, changed))
+        if underscore_to_dot or slashsubst or remotize:
+            branchnames = capture_or_die("git branch -l")
+            for branch in branchnames.split():
+                if branch:
+                    # Ugh - fragile dependency on branch -l output format
+                    branch = branch[2:]
+                    changed = branch
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if remotize:
+                        changed = os.path.join("remotes", remotize, branch)
+                    if changed != branch:
+                        do_or_die("branch --m %s %s >/dev/null" % (branch, changed))
+        if revisionmap:
+            refd = {}
+            for line in open(backend.revmap):
+                if line.startswith("#"):
+                    continue
+                (fn, rev, mark) = line.split()
+                refd[(fn, rev)] = mark
+            markd = {}
+            for line in open(markmap):
+                if line.startswith("#"):
+                    continue
+                (mark, hashd) = line.split()
+                markd[mark] = hashd
+            with open(metadata("cvs-revisions"), "a") as wfp:
+                for ((fn, rev), val) in refd.items():
+                    if val in markd:
+                        wfp.write("%s %s %s\n" % (fn, rev, markd[val]))
+            os.remove(markmap)
+            os.remove(backend.revmap)
+        if not import_only and not bare:
+            do_or_die("git checkout -q")
+    except Fatal, err:
+        sys.stderr.write("git_cvsimport: " + err.msg + "\n")
+        sys.exit(1)
+    except KeyboardInterrupt:
+        pass
+
+# end
diff --git a/t/t9601-cvsimport-vendor-branch.sh b/t/t9601-cvsimport-vendor-branch.sh
deleted file mode 100755
index 827d39f..0000000
--- a/t/t9601-cvsimport-vendor-branch.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/sh
-
-# Description of the files in the repository:
-#
-#    imported-once.txt:
-#
-#       Imported once.  1.1 and 1.1.1.1 should be identical.
-#
-#    imported-twice.txt:
-#
-#       Imported twice.  HEAD should reflect the contents of the
-#       second import (i.e., have the same contents as 1.1.1.2).
-#
-#    imported-modified.txt:
-#
-#       Imported, then modified on HEAD.  HEAD should reflect the
-#       modification.
-#
-#    imported-modified-imported.txt:
-#
-#       Imported, then modified on HEAD, then imported again.
-#
-#    added-imported.txt,v:
-#
-#       Added with 'cvs add' to create 1.1, then imported with
-#       completely different contents to create 1.1.1.1, therefore the
-#       vendor branch was never the default branch.
-#
-#    imported-anonymously.txt:
-#
-#       Like imported-twice.txt, but with a vendor branch whose branch
-#       tag has been removed.
-
-test_description='git cvsimport handling of vendor branches'
-. ./lib-cvs.sh
-
-setup_cvs_test_repository t9601
-
-test_expect_success PERL 'import a module with a vendor branch' '
-
-	git cvsimport -C module-git module
-
-'
-
-test_expect_success PERL 'check HEAD out of cvs repository' 'test_cvs_co master'
-
-test_expect_success PERL 'check master out of git repository' 'test_git_co master'
-
-test_expect_success PERL 'check a file that was imported once' '
-
-	test_cmp_branch_file master imported-once.txt
-
-'
-
-test_expect_failure PERL 'check a file that was imported twice' '
-
-	test_cmp_branch_file master imported-twice.txt
-
-'
-
-test_expect_success PERL 'check a file that was imported then modified on HEAD' '
-
-	test_cmp_branch_file master imported-modified.txt
-
-'
-
-test_expect_success PERL 'check a file that was imported, modified, then imported again' '
-
-	test_cmp_branch_file master imported-modified-imported.txt
-
-'
-
-test_expect_success PERL 'check a file that was added to HEAD then imported' '
-
-	test_cmp_branch_file master added-imported.txt
-
-'
-
-test_expect_success PERL 'a vendor branch whose tag has been removed' '
-
-	test_cmp_branch_file master imported-anonymously.txt
-
-'
-
-test_done
diff --git a/t/t9601/cvsroot/.gitattributes b/t/t9601/cvsroot/.gitattributes
deleted file mode 100644
index 562b12e..0000000
--- a/t/t9601/cvsroot/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* -whitespace
diff --git a/t/t9601/cvsroot/CVSROOT/.gitignore b/t/t9601/cvsroot/CVSROOT/.gitignore
deleted file mode 100644
index 3bb9b34..0000000
--- a/t/t9601/cvsroot/CVSROOT/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-history
-val-tags
diff --git a/t/t9601/cvsroot/module/added-imported.txt,v b/t/t9601/cvsroot/module/added-imported.txt,v
deleted file mode 100644
index 5f83072..0000000
--- a/t/t9601/cvsroot/module/added-imported.txt,v
+++ /dev/null
@@ -1,44 +0,0 @@
-head	1.1;
-access;
-symbols
-	vtag-4:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.15;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.16;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Add a file to the working copy.
-@
-text
-@Adding this file, before importing it with different contents.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-4).
-@
-text
-@d1 1
-a1 1
-This is vtag-4 (on vbranchA) of added-then-imported.txt.
-@
-
diff --git a/t/t9601/cvsroot/module/imported-anonymously.txt,v b/t/t9601/cvsroot/module/imported-anonymously.txt,v
deleted file mode 100644
index 55e1b0c..0000000
--- a/t/t9601/cvsroot/module/imported-anonymously.txt,v
+++ /dev/null
@@ -1,42 +0,0 @@
-head	1.1;
-branch	1.1.1;
-access;
-symbols
-	vtag-1:1.1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@This is vtag-1 (on vbranchA) of imported-anonymously.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
diff --git a/t/t9601/cvsroot/module/imported-modified-imported.txt,v b/t/t9601/cvsroot/module/imported-modified-imported.txt,v
deleted file mode 100644
index e5830ae..0000000
--- a/t/t9601/cvsroot/module/imported-modified-imported.txt,v
+++ /dev/null
@@ -1,76 +0,0 @@
-head	1.2;
-access;
-symbols
-	vtag-2:1.1.1.2
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2004.02.09.15.43.14;	author kfogel;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	1.1.1.2;
-
-1.1.1.2
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@First regular commit, to imported-modified-imported.txt, on HEAD.
-@
-text
-@This is a modification of imported-modified-imported.txt on HEAD.
-It should supersede the version from the vendor branch.
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d1 2
-a2 1
-This is vtag-1 (on vbranchA) of imported-modified-imported.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
-1.1.1.2
-log
-@Import (vbranchA, vtag-2).
-@
-text
-@d1 1
-a1 1
-This is vtag-2 (on vbranchA) of imported-modified-imported.txt.
-@
-
-
diff --git a/t/t9601/cvsroot/module/imported-modified.txt,v b/t/t9601/cvsroot/module/imported-modified.txt,v
deleted file mode 100644
index bbcfe44..0000000
--- a/t/t9601/cvsroot/module/imported-modified.txt,v
+++ /dev/null
@@ -1,59 +0,0 @@
-head	1.2;
-access;
-symbols
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2004.02.09.15.43.14;	author kfogel;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Commit on HEAD.
-@
-text
-@This is a modification of imported-modified.txt on HEAD.
-It should supersede the version from the vendor branch.
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d1 2
-a2 1
-This is vtag-1 (on vbranchA) of imported-modified.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
diff --git a/t/t9601/cvsroot/module/imported-once.txt,v b/t/t9601/cvsroot/module/imported-once.txt,v
deleted file mode 100644
index c5dd82b..0000000
--- a/t/t9601/cvsroot/module/imported-once.txt,v
+++ /dev/null
@@ -1,43 +0,0 @@
-head	1.1;
-branch	1.1.1;
-access;
-symbols
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@This is vtag-1 (on vbranchA) of imported-once.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
diff --git a/t/t9601/cvsroot/module/imported-twice.txt,v b/t/t9601/cvsroot/module/imported-twice.txt,v
deleted file mode 100644
index d1f3f1b..0000000
--- a/t/t9601/cvsroot/module/imported-twice.txt,v
+++ /dev/null
@@ -1,60 +0,0 @@
-head	1.1;
-branch	1.1.1;
-access;
-symbols
-	vtag-2:1.1.1.2
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	1.1.1.2;
-
-1.1.1.2
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@This is vtag-1 (on vbranchA) of imported-twice.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
-1.1.1.2
-log
-@Import (vbranchA, vtag-2).
-@
-text
-@d1 1
-a1 1
-This is vtag-2 (on vbranchA) of imported-twice.txt.
-@
-
-
diff --git a/t/t9602-cvsimport-branches-tags.sh b/t/t9602-cvsimport-branches-tags.sh
deleted file mode 100755
index e1db323..0000000
--- a/t/t9602-cvsimport-branches-tags.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-
-# A description of the repository used for this test can be found in
-# t9602/README.
-
-test_description='git cvsimport handling of branches and tags'
-. ./lib-cvs.sh
-
-setup_cvs_test_repository t9602
-
-test_expect_success PERL 'import module' '
-
-	git cvsimport -C module-git module
-
-'
-
-test_expect_success PERL 'test branch master' '
-
-	test_cmp_branch_tree master
-
-'
-
-test_expect_success PERL 'test branch vendorbranch' '
-
-	test_cmp_branch_tree vendorbranch
-
-'
-
-test_expect_failure PERL 'test branch B_FROM_INITIALS' '
-
-	test_cmp_branch_tree B_FROM_INITIALS
-
-'
-
-test_expect_failure PERL 'test branch B_FROM_INITIALS_BUT_ONE' '
-
-	test_cmp_branch_tree B_FROM_INITIALS_BUT_ONE
-
-'
-
-test_expect_failure PERL 'test branch B_MIXED' '
-
-	test_cmp_branch_tree B_MIXED
-
-'
-
-test_expect_success PERL 'test branch B_SPLIT' '
-
-	test_cmp_branch_tree B_SPLIT
-
-'
-
-test_expect_failure PERL 'test tag vendortag' '
-
-	test_cmp_branch_tree vendortag
-
-'
-
-test_expect_success PERL 'test tag T_ALL_INITIAL_FILES' '
-
-	test_cmp_branch_tree T_ALL_INITIAL_FILES
-
-'
-
-test_expect_failure PERL 'test tag T_ALL_INITIAL_FILES_BUT_ONE' '
-
-	test_cmp_branch_tree T_ALL_INITIAL_FILES_BUT_ONE
-
-'
-
-test_expect_failure PERL 'test tag T_MIXED' '
-
-	test_cmp_branch_tree T_MIXED
-
-'
-
-
-test_done
diff --git a/t/t9602/README b/t/t9602/README
deleted file mode 100644
index c231e0f..0000000
--- a/t/t9602/README
+++ /dev/null
@@ -1,62 +0,0 @@
-This repository is for testing the ability to group revisions
-correctly along tags and branches.  Here is its history:
-
-  1.  The initial import (revision 1.1 of everybody) created a
-      directory structure with a file named `default' in each dir:
-
-            ./
-              default
-              sub1/default
-                   subsubA/default
-                   subsubB/default
-              sub2/default
-                   subsubA/default
-              sub3/default
-
-  2.  Then tagged everyone with T_ALL_INITIAL_FILES.
-
-  3.  Then tagged everyone except sub1/subsubB/default with
-      T_ALL_INITIAL_FILES_BUT_ONE.
-
-  4.  Then created branch B_FROM_INITIALS on everyone.
-
-  5.  Then created branch B_FROM_INITIALS_BUT_ONE on everyone except
-      /sub1/subsubB/default.
-
-  6.  Then committed modifications to two files: sub3/default, and
-      sub1/subsubA/default.
-
-  7.  Then committed a modification to all 7 files.
-
-  8.  Then backdated sub3/default to revision 1.2, and
-      sub2/subsubA/default to revision 1.1, and tagged with T_MIXED.
-
-  9.  Same as 8, but tagged with -b to create branch B_MIXED.
-
-  10. Switched the working copy to B_MIXED, and added
-      sub2/branch_B_MIXED_only.  (That's why the RCS file is in
-      sub2/Attic/ -- it never existed on trunk.)
-
-  11. In one commit, modified default, sub1/default, and
-      sub2/subsubA/default, on branch B_MIXED.
-
-  12. Did "cvs up -A" on sub2/default, then in one commit, made a
-      change to sub2/default and sub2/branch_B_MIXED_only.  So this
-      commit should be spread between the branch and the trunk.
-
-  13. Do "cvs up -A" to get everyone back to trunk, then make a new
-      branch B_SPLIT on everyone except sub1/subsubB/default,v.
-
-  14. Switch to branch B_SPLIT (see sub1/subsubB/default disappear)
-      and commit a change that affects everyone except sub3/default.
-
-  15. An hour or so later, "cvs up -A" to get sub1/subsubB/default
-      back, then commit a change on that file, on trunk.  (It's
-      important that this change happened after the previous commits
-      on B_SPLIT.)
-
-  16. Branch sub1/subsubB/default to B_SPLIT, then "cvs up -r B_SPLIT"
-      to switch the whole working copy to the branch.
-
-  17. Commit a change on B_SPLIT, to sub1/subsubB/default and
-      sub3/default.
diff --git a/t/t9602/cvsroot/.gitattributes b/t/t9602/cvsroot/.gitattributes
deleted file mode 100644
index 562b12e..0000000
--- a/t/t9602/cvsroot/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* -whitespace
diff --git a/t/t9602/cvsroot/CVSROOT/.gitignore b/t/t9602/cvsroot/CVSROOT/.gitignore
deleted file mode 100644
index 3bb9b34..0000000
--- a/t/t9602/cvsroot/CVSROOT/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-history
-val-tags
diff --git a/t/t9602/cvsroot/module/default,v b/t/t9602/cvsroot/module/default,v
deleted file mode 100644
index 3b68382..0000000
--- a/t/t9602/cvsroot/module/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.2;
-access;
-symbols
-	B_SPLIT:1.2.0.4
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.2.2.1
-	1.2.4.1;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2003.05.23.00.31.36;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.4.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is the file `default' in the top level of the project.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.2.4.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a5 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2.2.1
-log
-@Modify three files, on branch B_MIXED.
-@
-text
-@a5 2
-
-This line was added on branch B_MIXED only (affecting 3 files).
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub1/default,v b/t/t9602/cvsroot/module/sub1/default,v
deleted file mode 100644
index b7fdccd..0000000
--- a/t/t9602/cvsroot/module/sub1/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.2;
-access;
-symbols
-	B_SPLIT:1.2.0.4
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.2.2.1
-	1.2.4.1;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2003.05.23.00.31.36;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.4.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub1/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.2.4.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a5 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2.2.1
-log
-@Modify three files, on branch B_MIXED.
-@
-text
-@a5 2
-
-This line was added on branch B_MIXED only (affecting 3 files).
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub1/subsubA/default,v b/t/t9602/cvsroot/module/sub1/subsubA/default,v
deleted file mode 100644
index 472b7b2..0000000
--- a/t/t9602/cvsroot/module/sub1/subsubA/default,v
+++ /dev/null
@@ -1,101 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.4
-	B_MIXED:1.3.0.2
-	T_MIXED:1.3
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.3.4.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.15.26;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.4.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub1/subsubA/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added by the first commit (affecting two files).
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.3.4.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a7 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2
-log
-@First commit to proj, affecting two files.
-@
-text
-@d6 2
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub1/subsubB/default,v b/t/t9602/cvsroot/module/sub1/subsubB/default,v
deleted file mode 100644
index fe6efa4..0000000
--- a/t/t9602/cvsroot/module/sub1/subsubB/default,v
+++ /dev/null
@@ -1,107 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.2
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.06.03.04.29.14;	author jrandom;	state Exp;
-branches
-	1.3.2.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.2.1
-date	2003.06.03.04.33.13;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@A trunk change to sub1/subsubB/default.  This was committed about an
-hour after an earlier change that affected most files on branch
-B_SPLIT.  This file is not on that branch yet, but after this commit,
-we'll branch to B_SPLIT, albeit rooted in a revision that didn't exist
-at the time the rest of B_SPLIT was created.
-@
-text
-@This is sub1/subsubB/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-
-This bit was committed on trunk about an hour after an earlier change
-to everyone else on branch B_SPLIT.  Afterwards, we'll finally branch
-this file to B_SPLIT, but rooted in a revision that didn't exist at
-the time the rest of B_SPLIT was created.
-@
-
-
-1.3.2.1
-log
-@This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-text
-@a10 4
-
-This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@d6 5
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v b/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v
deleted file mode 100644
index 34c9789..0000000
--- a/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v
+++ /dev/null
@@ -1,59 +0,0 @@
-head	1.1;
-access;
-symbols
-	B_MIXED:1.1.0.2;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2003.05.23.00.25.26;	author jrandom;	state dead;
-branches
-	1.1.2.1;
-next	;
-
-1.1.2.1
-date	2003.05.23.00.25.26;	author jrandom;	state Exp;
-branches;
-next	1.1.2.2;
-
-1.1.2.2
-date	2003.05.23.00.48.51;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@file branch_B_MIXED_only was initially added on branch B_MIXED.
-@
-text
-@@
-
-
-1.1.2.1
-log
-@Add a file on branch B_MIXED.
-@
-text
-@a0 1
-This file was added on branch B_MIXED.  It never existed on trunk.
-@
-
-
-1.1.2.2
-log
-@A single commit affecting one file on branch B_MIXED and one on trunk.
-@
-text
-@a1 3
-
-The same commit added these two lines here on branch B_MIXED, and two
-similar lines to ./default on trunk.
-@
-
-
diff --git a/t/t9602/cvsroot/module/sub2/default,v b/t/t9602/cvsroot/module/sub2/default,v
deleted file mode 100644
index 018f7f8..0000000
--- a/t/t9602/cvsroot/module/sub2/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.2
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.05.23.00.48.51;	author jrandom;	state Exp;
-branches
-	1.3.2.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.2.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@A single commit affecting one file on branch B_MIXED and one on trunk.
-@
-text
-@This is sub2/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-
-The same commit added these two lines here on trunk, and two similar
-lines to ./branch_B_MIXED_only on branch B_MIXED.
-@
-
-
-1.3.2.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a8 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@d6 3
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub2/subsubA/default,v b/t/t9602/cvsroot/module/sub2/subsubA/default,v
deleted file mode 100644
index d13242c..0000000
--- a/t/t9602/cvsroot/module/sub2/subsubA/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.2;
-access;
-symbols
-	B_SPLIT:1.2.0.2
-	B_MIXED:1.1.0.2
-	T_MIXED:1.1
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.2.2.1;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1
-	1.1.2.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.1.2.1
-date	2003.05.23.00.31.36;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub2/subsub2/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.2.2.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a5 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.2.1
-log
-@Modify three files, on branch B_MIXED.
-@
-text
-@a3 2
-
-This line was added on branch B_MIXED only (affecting 3 files).
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub3/default,v b/t/t9602/cvsroot/module/sub3/default,v
deleted file mode 100644
index 88e4567..0000000
--- a/t/t9602/cvsroot/module/sub3/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.2
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.3.2.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.15.26;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.2.1
-date	2003.06.03.04.33.13;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub3/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added by the first commit (affecting two files).
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.3.2.1
-log
-@This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-text
-@a7 4
-
-This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-
-
-1.2
-log
-@First commit to proj, affecting two files.
-@
-text
-@d6 2
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh
deleted file mode 100755
index 52034c8..0000000
--- a/t/t9603-cvsimport-patchsets.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/sh
-
-# Structure of the test cvs repository
-#
-# Message   File:Content         Commit Time
-# Rev 1     a: 1.1               2009-02-21 19:11:43 +0100
-# Rev 2     a: 1.2    b: 1.1     2009-02-21 19:11:14 +0100
-# Rev 3               b: 1.2     2009-02-21 19:11:43 +0100
-#
-# As you can see the commit of Rev 3 has the same time as
-# Rev 1 this leads to a broken import because of a cvsps
-# bug.
-
-test_description='git cvsimport testing for correct patchset estimation'
-. ./lib-cvs.sh
-
-setup_cvs_test_repository t9603
-
-test_expect_failure 'import with criss cross times on revisions' '
-
-    git cvsimport -p"-x" -C module-git module &&
-    (cd module-git &&
-        git log --pretty=format:%s > ../actual-master &&
-        git log A~2..A --pretty="format:%s %ad" -- > ../actual-A &&
-        echo "" >> ../actual-master &&
-	echo "" >> ../actual-A
-    ) &&
-    echo "Rev 4
-Rev 3
-Rev 2
-Rev 1" > expect-master &&
-    test_cmp actual-master expect-master &&
-
-    echo "Rev 5 Branch A Wed Mar 11 19:09:10 2009 +0000
-Rev 4 Branch A Wed Mar 11 19:03:52 2009 +0000" > expect-A &&
-    test_cmp actual-A expect-A
-'
-
-test_done
diff --git a/t/t9603/cvsroot/.gitattributes b/t/t9603/cvsroot/.gitattributes
deleted file mode 100644
index 562b12e..0000000
--- a/t/t9603/cvsroot/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* -whitespace
diff --git a/t/t9603/cvsroot/CVSROOT/.gitignore b/t/t9603/cvsroot/CVSROOT/.gitignore
deleted file mode 100644
index 3bb9b34..0000000
--- a/t/t9603/cvsroot/CVSROOT/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-history
-val-tags
diff --git a/t/t9603/cvsroot/module/a,v b/t/t9603/cvsroot/module/a,v
deleted file mode 100644
index ba8fd5a..0000000
--- a/t/t9603/cvsroot/module/a,v
+++ /dev/null
@@ -1,74 +0,0 @@
-head	1.2;
-access;
-symbols
-	A:1.2.0.2;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2009.02.21.18.11.14;	author tester;	state Exp;
-branches
-	1.2.2.1;
-next	1.1;
-
-1.1
-date	2009.02.21.18.11.43;	author tester;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2009.03.11.19.03.52;	author tester;	state Exp;
-branches;
-next	1.2.2.2;
-
-1.2.2.2
-date	2009.03.11.19.09.10;	author tester;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Rev 2
-@
-text
-@1.2
-@
-
-
-1.2.2.1
-log
-@Rev 4 Branch A
-@
-text
-@d1 1
-a1 1
-1.2.2.1
-@
-
-
-1.2.2.2
-log
-@Rev 5 Branch A
-@
-text
-@d1 1
-a1 1
-1.2.2.2
-@
-
-
-1.1
-log
-@Rev 1
-@
-text
-@d1 1
-a1 1
-1.1
-@
diff --git a/t/t9603/cvsroot/module/b,v b/t/t9603/cvsroot/module/b,v
deleted file mode 100644
index d268855..0000000
--- a/t/t9603/cvsroot/module/b,v
+++ /dev/null
@@ -1,90 +0,0 @@
-head	1.3;
-access;
-symbols
-	A:1.2.0.2;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2009.03.11.19.05.08;	author tester;	state Exp;
-branches;
-next	1.2;
-
-1.2
-date	2009.02.21.18.11.43;	author tester;	state Exp;
-branches
-	1.2.2.1;
-next	1.1;
-
-1.1
-date	2009.02.21.18.11.14;	author tester;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2009.03.11.19.03.52;	author tester;	state Exp;
-branches;
-next	1.2.2.2;
-
-1.2.2.2
-date	2009.03.11.19.09.10;	author tester;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@Rev 4
-@
-text
-@1.3
-@
-
-
-1.2
-log
-@Rev 3
-@
-text
-@d1 1
-a1 1
-1.2
-@
-
-
-1.2.2.1
-log
-@Rev 4 Branch A
-@
-text
-@d1 1
-a1 1
-1.2.2.1
-@
-
-
-1.2.2.2
-log
-@Rev 5 Branch A
-@
-text
-@d1 1
-a1 1
-1.2
-@
-
-
-1.1
-log
-@Rev 2
-@
-text
-@d1 1
-a1 1
-1.1
-@
-- 
1.8.1.rc2



-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>

             reply	other threads:[~2013-01-01 17:27 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-01-01 17:26 Eric S. Raymond [this message]
2013-01-01 21:54 ` [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs Junio C Hamano
2013-01-02  0:33   ` Eric S. Raymond
2013-01-02  1:06     ` Junio C Hamano
2013-01-02  8:02     ` Jonathan Nieder
2013-01-02 10:59       ` Eric S. Raymond
2013-01-02 15:39         ` Jonathan Nieder
2013-01-02 16:18           ` Eric S. Raymond
2013-01-02 16:32             ` Martin Langhoff
2013-01-02 16:41               ` Eric S. Raymond
2013-01-02 16:48                 ` Thomas Berg
2013-01-02 21:15                 ` Martin Langhoff
2013-01-02 22:28                   ` Eric S. Raymond
2013-01-02 23:44                     ` Martin Langhoff
2013-01-02 16:35             ` Jonathan Nieder
2013-01-02 16:43             ` Andreas Schwab
2013-01-02 18:08     ` Junio C Hamano
2013-01-02 18:37       ` Eric S. Raymond
2013-01-02 19:07         ` Junio C Hamano
2013-01-03  6:34 ` Chris Rorvick
2013-01-03  7:08   ` Junio C Hamano
2013-01-03  7:47     ` Antoine Pelisse
2013-01-03 15:22       ` Junio C Hamano
2013-01-03 16:24         ` Michael Haggerty

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20130101172645.GA5506@thyrsus.com \
    --to=esr@thyrsus.com \
    --cc=git@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.