{"thread":{"id":"32498","subject":"[PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","startedAt":"2013-01-01T17:26:45Z","lastAt":"2013-01-03T16:24:18Z","messageCount":24,"participants":["Eric S. Raymond","Junio C Hamano","Jonathan Nieder","Martin Langhoff","Andreas Schwab","Thomas Berg","Chris Rorvick","Antoine Pelisse","Michael Haggerty"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"205772","messageId":"20130101172645.GA5506@thyrsus.com","threadId":"32498","inReplyTo":null,"subject":"[PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Eric S. Raymond","fromEmail":"esr@thyrsus.com","sentAt":"2013-01-01T17:26:45Z","receivedAt":"2013-01-01T17:26:45Z","isPatch":true,"sender":{"key":"esr@thyrsus.com","avatar":"https://avatars.githubusercontent.com/u/727961?v=4"},"body":"The combination of git-cvsimport and cvsps had serious problems.\nAmong these were:\n\n(1) Analysis of branchy repos was buggy in multiple ways in both\n    programs, leading to incorrect repo translations.\n\n(2) Even after a correct branch analysis, extra (redundant) fileops\n    would often be generated on the new-branch side.\n\n(3) Inability to report more than one tag pointing to the same revision.\n\n(4) Failure in certain cases of clock-skew reported by the t9603 test.\n\n(5) Failure to use commitids for changeset ordering in cases were this\n    would have prevented clock skew from causing incorrect grouping.\n\nProblems 2-5 and portions of problem 1 have been solved by a major\nrewrite of cvsps (the 3.x release series); it now emits a git\nfast-import stream.  Also, the buggy attempt at ancestry-branch\ntracking previously invoked by -A has been replaced with a simpler and\nbetter topo analysis.  cvsps is now about 20% smaller than formerly.\n\nAll this changed cvsps's interface enough to require a complete\nrewrite of git-cvsimport (hence this patch). In the process the code\nsize of the wrapper script dropped by about x3 and it can now support\nalternate conversion engines; the first new engine is cvs2git, with\nparsecvs expected to follow shortly if that code proves salvageable.\n\nThis patch also removes Michael Haggerty's git-cvsimport tests from\nthe git tree.  These are actually conversion-engine tests and have been\nmerged into a larger cvsps test suite, which I intend to spin out into\na general CVS-lifting test that can also be applied to utilities such\nas cvs2git and parsecvs.\n\nThe following known bug has not been fixed: \"If any files were ever \"cvs\nimport\"ed more than once (e.g., import of more than one vendor\nrelease) the HEAD contains the wrong content.\" However, cvsps now\nemits a warning in this case. There is also one pathological tagging\ncase that was successful the former t9602 test that now fails (with\na warning).\n\nI plan to address these problems. This patch at least gets the\ncvsps-3.x/git-cvsimport combination to a state that is not too\nbroken to ship - that is, in all failure cases known to me it\nnow emits useful warnings rather than silently botching the\nimport.\n---\n Documentation/git-cvsimport.txt                    |  303 +++--\n Makefile                                           |    2 +-\n git-cvsimport.perl                                 | 1177 --------------------\n git-cvsimport.py                                   |  342 ++++++\n t/t9601-cvsimport-vendor-branch.sh                 |   85 --\n t/t9601/cvsroot/.gitattributes                     |    1 -\n t/t9601/cvsroot/CVSROOT/.gitignore                 |    2 -\n t/t9601/cvsroot/module/added-imported.txt,v        |   44 -\n t/t9601/cvsroot/module/imported-anonymously.txt,v  |   42 -\n .../module/imported-modified-imported.txt,v        |   76 --\n t/t9601/cvsroot/module/imported-modified.txt,v     |   59 -\n t/t9601/cvsroot/module/imported-once.txt,v         |   43 -\n t/t9601/cvsroot/module/imported-twice.txt,v        |   60 -\n t/t9602-cvsimport-branches-tags.sh                 |   78 --\n t/t9602/README                                     |   62 --\n t/t9602/cvsroot/.gitattributes                     |    1 -\n t/t9602/cvsroot/CVSROOT/.gitignore                 |    2 -\n t/t9602/cvsroot/module/default,v                   |  102 --\n t/t9602/cvsroot/module/sub1/default,v              |  102 --\n t/t9602/cvsroot/module/sub1/subsubA/default,v      |  101 --\n t/t9602/cvsroot/module/sub1/subsubB/default,v      |  107 --\n .../module/sub2/Attic/branch_B_MIXED_only,v        |   59 -\n t/t9602/cvsroot/module/sub2/default,v              |  102 --\n t/t9602/cvsroot/module/sub2/subsubA/default,v      |  102 --\n t/t9602/cvsroot/module/sub3/default,v              |  102 --\n t/t9603-cvsimport-patchsets.sh                     |   39 -\n t/t9603/cvsroot/.gitattributes                     |    1 -\n t/t9603/cvsroot/CVSROOT/.gitignore                 |    2 -\n t/t9603/cvsroot/module/a,v                         |   74 --\n t/t9603/cvsroot/module/b,v                         |   90 --\n 30 files changed, 492 insertions(+), 2870 deletions(-)\n delete mode 100755 git-cvsimport.perl\n create mode 100755 git-cvsimport.py\n delete mode 100755 t/t9601-cvsimport-vendor-branch.sh\n delete mode 100644 t/t9601/cvsroot/.gitattributes\n delete mode 100644 t/t9601/cvsroot/CVSROOT/.gitignore\n delete mode 100644 t/t9601/cvsroot/module/added-imported.txt,v\n delete mode 100644 t/t9601/cvsroot/module/imported-anonymously.txt,v\n delete mode 100644 t/t9601/cvsroot/module/imported-modified-imported.txt,v\n delete mode 100644 t/t9601/cvsroot/module/imported-modified.txt,v\n delete mode 100644 t/t9601/cvsroot/module/imported-once.txt,v\n delete mode 100644 t/t9601/cvsroot/module/imported-twice.txt,v\n delete mode 100755 t/t9602-cvsimport-branches-tags.sh\n delete mode 100644 t/t9602/README\n delete mode 100644 t/t9602/cvsroot/.gitattributes\n delete mode 100644 t/t9602/cvsroot/CVSROOT/.gitignore\n delete mode 100644 t/t9602/cvsroot/module/default,v\n delete mode 100644 t/t9602/cvsroot/module/sub1/default,v\n delete mode 100644 t/t9602/cvsroot/module/sub1/subsubA/default,v\n delete mode 100644 t/t9602/cvsroot/module/sub1/subsubB/default,v\n delete mode 100644 t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v\n delete mode 100644 t/t9602/cvsroot/module/sub2/default,v\n delete mode 100644 t/t9602/cvsroot/module/sub2/subsubA/default,v\n delete mode 100644 t/t9602/cvsroot/module/sub3/default,v\n delete mode 100755 t/t9603-cvsimport-patchsets.sh\n delete mode 100644 t/t9603/cvsroot/.gitattributes\n delete mode 100644 t/t9603/cvsroot/CVSROOT/.gitignore\n delete mode 100644 t/t9603/cvsroot/module/a,v\n delete mode 100644 t/t9603/cvsroot/module/b,v\n\ndiff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt\nindex 98d9881..92da652 100644\n--- a/Documentation/git-cvsimport.txt\n+++ b/Documentation/git-cvsimport.txt\n@@ -9,75 +9,78 @@ git-cvsimport - Salvage your data out of another SCM people love to hate\n SYNOPSIS\n --------\n [verse]\n-'git cvsimport' [-o <branch-for-HEAD>] [-h] [-v] [-d <CVSROOT>]\n-\t      [-A <author-conv-file>] [-p <options-for-cvsps>] [-P <file>]\n-\t      [-C <git_repository>] [-z <fuzz>] [-i] [-k] [-u] [-s <subst>]\n-\t      [-a] [-m] [-M <regex>] [-S <regex>] [-L <commitlimit>]\n-\t      [-r <remote>] [-R] [<CVS_module>]\n-\n+'git cvsimport' [-A <author-conv-file>] [-b] [-C <git_repository>] \n+              [-d <CVSROOT>] [-h] [-i] [-k] [-p <options-for-engine>] \n+\t      [-P <cvsps-output-file>] [-r <remote>] [-R] [-s <subst>] \n+\t      [-S <regex>] [-u] [-v] [-z <fuzz>] [<CVS_module>]\n \n DESCRIPTION\n -----------\n-Imports a CVS repository into git. It will either create a new\n+Imports a CVS repository into git. This tool will either create a new\n repository, or incrementally import into an existing one.\n \n-Splitting the CVS log into patch sets is done by 'cvsps'.\n-At least version 2.1 is required.\n-\n-*WARNING:* for certain situations the import leads to incorrect results.\n-Please see the section <<issues,ISSUES>> for further reference.\n-\n-You should *never* do any work of your own on the branches that are\n-created by 'git cvsimport'.  By default initial import will create and populate a\n-\"master\" branch from the CVS repository's main branch which you're free\n-to work with; after that, you need to 'git merge' incremental imports, or\n-any CVS branches, yourself.  It is advisable to specify a named remote via\n--r to separate and protect the incoming branches.\n-\n-If you intend to set up a shared public repository that all developers can\n-read/write, or if you want to use linkgit:git-cvsserver[1], then you\n-probably want to make a bare clone of the imported repository,\n-and use the clone as the shared repository.\n-See linkgit:gitcvs-migration[7].\n+*WARNING:* The CVS model of version control lends itself to all manner\n+of perversities; not all sequences of CVS operations can be translated\n+into an import stream, and importing is not guaranteed to produce a\n+perfectly accurate representation of CVS history. Please see the\n+section on <<issues,engine-specific issues>> for further reference.\n \n+git cvsimport will do well at translating CVS repositories with a\n+linear or close-to-linear revision history, no merges, and\n+well-disciplined tagging practices.  More complex cases will require\n+human judgment amplified by a repository-editing tool such as\n+http://www.catb.org/~esr/reposurgeon[reposurgeon].\n \n OPTIONS\n -------\n--v::\n-\tVerbosity: let 'cvsimport' report what it is doing.\n-\n--d <CVSROOT>::\n-\tThe root of the CVS archive. May be local (a simple path) or remote;\n-\tcurrently, only the :local:, :ext: and :pserver: access methods\n-\tare supported. If not given, 'git cvsimport' will try to read it\n-\tfrom `CVS/Root`. If no such file exists, it checks for the\n-\t`CVSROOT` environment variable.\n+-A <author-conv-file>::\n+\tCVS by default uses the Unix username when writing its\n+\tcommit logs. Using this option and an author-conv-file\n+\tmaps the name recorded in CVS to author name, e-mail and\n+\toptional timezone:\n++\n+---------\n+\texon=Andreas Ericsson <ae@op5.se> +0200\n+\tspawn=Simon Pawn <spawn@frog-pond.org> -0500\n+---------\n++\n+'git cvsimport' will make it appear as those authors had\n+their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly\n+all along.\n++\n+For convenience, this data is saved to `$GIT_DIR/cvs-authors`\n+each time the '-A' option is provided and read from that same\n+file each time 'git cvsimport' is run.\n++\n+It is not recommended to use this feature if you intend to\n+export changes back to CVS again later with\n+'git cvsexportcommit'.\n \n-<CVS_module>::\n-\tThe CVS module you want to import. Relative to <CVSROOT>.\n-\tIf not given, 'git cvsimport' tries to read it from\n-\t`CVS/Repository`.\n+-b::\n+        Create a bare repo. If you intend to set up a shared public\n+\trepository that all developers can read/write, or if you want\n+\tto use linkgit:git-cvsserver[1], then you probably want to\n+\tmake a bare clone of the imported repository using this\n+\toption. See linkgit:gitcvs-migration[7].\n \n -C <target-dir>::\n         The git repository to import to.  If the directory doesn't\n         exist, it will be created.  Default is the current directory.\n \n--r <remote>::\n-\tThe git remote to import this CVS repository into.\n-\tMoves all CVS branches into remotes/<remote>/<branch>\n-\takin to the way 'git clone' uses 'origin' by default.\n+-d <CVSROOT>::\n+\tThe root of the CVS archive. It is only necessary to specify\n+\tthis option if you are running from somewhere other than a \n+\tCVS checkout directory; the value is passed to the conversion\n+\tengine to be interpreted.\n \n--o <branch-for-HEAD>::\n-\tWhen no remote is specified (via -r) the 'HEAD' branch\n-\tfrom CVS is imported to the 'origin' branch within the git\n-\trepository, as 'HEAD' already has a special meaning for git.\n-\tWhen a remote is specified the 'HEAD' branch is named\n-\tremotes/<remote>/master mirroring 'git clone' behaviour.\n-\tUse this option if you want to import into a different\n-\tbranch.\n-+\n-Use '-o master' for continuing an import that was initially done by\n-the old cvs2git tool.\n+-e <engine>::\n+\tSplitting the CVS log into patch sets is done by an engine program,\n+\twhich must emit a git fast-import stream to standard output.\n+\tThis option changes the engine used; when given, it must be the\n+\tfirst option on the command line.\n+\n+-h::\n+\tPrint a short usage message and exit.\n \n -i::\n \tImport-only: don't perform a checkout after importing.  This option\n@@ -89,75 +92,19 @@ the old cvs2git tool.\n \tto avoid noisy changesets. Highly recommended, but off by default\n \tto preserve compatibility with early imported trees.\n \n--u::\n-\tConvert underscores in tag and branch names to dots.\n-\n--s <subst>::\n-\tSubstitute the character \"/\" in branch names with <subst>\n-\n--p <options-for-cvsps>::\n-\tAdditional options for cvsps.\n-\tThe options '-u' and '-A' are implicit and should not be used here.\n-+\n-If you need to pass multiple options, separate them with a comma.\n-\n--z <fuzz>::\n-\tPass the timestamp fuzz factor to cvsps, in seconds. If unset,\n-\tcvsps defaults to 300s.\n-\n -P <cvsps-output-file>::\n-\tInstead of calling cvsps, read the provided cvsps output file. Useful\n-\tfor debugging or when cvsps is being handled outside cvsimport.\n-\n--m::\n-\tAttempt to detect merges based on the commit message. This option\n-\twill enable default regexes that try to capture the source\n-\tbranch name from the commit message.\n-\n--M <regex>::\n-\tAttempt to detect merges based on the commit message with a custom\n-\tregex. It can be used with '-m' to enable the default regexes\n-\tas well. You must escape forward slashes.\n-+\n-The regex must capture the source branch name in $1.\n-+\n-This option can be used several times to provide several detection regexes.\n-\n--S <regex>::\n-\tSkip paths matching the regex.\n+\tInstead of calling a conversion engine, read the provided\n+\timport-stream file. Useful for debugging or when the first\n+\tstage of conversion is being handled outside cvsimport.\n \n--a::\n-\tImport all commits, including recent ones. cvsimport by default\n-\tskips commits that have a timestamp less than 10 minutes ago.\n-\n--L <limit>::\n-\tLimit the number of commits imported. Workaround for cases where\n-\tcvsimport leaks memory.\n-\n--A <author-conv-file>::\n-\tCVS by default uses the Unix username when writing its\n-\tcommit logs. Using this option and an author-conv-file\n-\tmaps the name recorded in CVS to author name, e-mail and\n-\toptional timezone:\n-+\n----------\n-\texon=Andreas Ericsson <ae@op5.se>\n-\tspawn=Simon Pawn <spawn@frog-pond.org> America/Chicago\n+-r <remote>::\n+\tThe git remote to import this CVS repository into.\n+\tMoves all CVS branches into remotes/<remote>/<branch>\n+\takin to the way 'git clone' uses 'origin' by default.\n \n----------\n-+\n-'git cvsimport' will make it appear as those authors had\n-their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly\n-all along.  If a timezone is specified, GIT_AUTHOR_DATE will\n-have the corresponding offset applied.\n-+\n-For convenience, this data is saved to `$GIT_DIR/cvs-authors`\n-each time the '-A' option is provided and read from that same\n-file each time 'git cvsimport' is run.\n-+\n-It is not recommended to use this feature if you intend to\n-export changes back to CVS again later with\n-'git cvsexportcommit'.\n+-p <options-for-engine>::\n+\tAdditional options for the engine. If you need to pass\n+\tmultiple options, separate them with a comma.\n \n -R::\n \tGenerate a `$GIT_DIR/cvs-revisions` file containing a mapping from CVS\n@@ -174,50 +121,98 @@ doing incremental imports.\n +\n This option may be useful if you have CVS revision numbers stored in commit\n messages, bug-tracking systems, email archives, and the like.\n++\n+-s <subst>::\n+\tSubstitute the character \"/\" in branch names with <subst>\n \n--h::\n-\tPrint a short usage message and exit.\n-\n-OUTPUT\n-------\n-If '-v' is specified, the script reports what it is doing.\n-\n-Otherwise, success is indicated the Unix way, i.e. by simply exiting with\n-a zero exit status.\n+-S <regex>::\n+\tSkip paths matching the regex.\n \n-[[issues]]\n-ISSUES\n-------\n-Problems related to timestamps:\n+-u::\n+\tConvert underscores in tag and branch names to dots.\n \n- * If timestamps of commits in the CVS repository are not stable enough\n-   to be used for ordering commits changes may show up in the wrong\n-   order.\n- * If any files were ever \"cvs import\"ed more than once (e.g., import of\n-   more than one vendor release) the HEAD contains the wrong content.\n- * If the timestamp order of different files cross the revision order\n-   within the commit matching time window the order of commits may be\n-   wrong.\n+-v::\n+\tVerbosity: let 'cvsimport' report what it is doing.\n \n-Problems related to branches:\n+-z <fuzz>::\n+\tPass the timestamp fuzz factor, in seconds. If unset, this has \n+\tan engine-dependent default - usually 300s.\n \n- * Branches on which no commits have been made are not imported.\n- * All files from the branching point are added to a branch even if\n-   never added in CVS.\n- * This applies to files added to the source branch *after* a daughter\n-   branch was created: if previously no commit was made on the daughter\n-   branch they will erroneously be added to the daughter branch in git.\n+<CVS_module>::\n+\tThe CVS module you want to import. Relative to <CVSROOT>.  It\n+\tis only necessary to specify this option if you are running\n+\tfrom somewhere other than a CVS checkout directory; the value\n+\tis passed to the conversion engine to be interpreted.\n \n-Problems related to tags:\n+OUTPUT\n+------\n+If '-v' is specified, the program reports what it is doing.\n \n-* Multiple tags on the same revision are not imported.\n+Otherwise, success is indicated the Unix way, i.e. by simply exiting with\n+a zero exit status.\n \n-If you suspect that any of these issues may apply to the repository you\n-want to import consider using these alternative tools which proved to be\n-more stable in practice:\n+[[compatibility]]\n+COMPATIBILITY\n+-------------\n+In 2012 two serious bugs dating back to 2006 in cvsps were exposed.  The\n+ancestry-branch tracking formerly enabled by -A did not work, and\n+branch detection was generally buggy; translations of branchy repos\n+could be mangled.  While the --fast-export mode in 3.x releases of\n+cvsps solved the problem, it required an emergency rewrite of\n+git-cvsimport.  Some compatibility with older versions was unavoidably\n+lost\n+\n+The -a, -o, -m, -M, and -L options in older versions of this tool have\n+been removed (in effect, -a is always on; you can negate it with\n+suitably crafted -d arguments).  Certain older versions could take a\n+named timezone (like \"America/Chicago\") in an author-map file rather\n+than just a [+-]hhmm offset; this version doesn't do that, but the\n+capability may be restored in a future release.\n \n-* cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org`\n-* parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs`\n+[[issues]]\n+ENGINE-SPECIFIC ISSUES\n+----------------------\n+The conversion engines try to warn you about repository histories they\n+can't handle; see their individual manual pages to learn how to\n+interpret the warnings you may receive.\n+\n+The default conversion engine is 'cvsps'.  If warnings you receive\n+suggest that the repository translation is invalid, consider switching\n+engines to 'cvs2git'.\n+\n+cvsps\n+~~~~~\n+The default conversion engine is 'cvsps'; at least version 3.3 is\n+required.  The cvsps project page is at `http://www.catb.org/~esr/cvsps`.\n+Things to know about this engine:\n+\n+* As well as working from within a CVS checkout directory,\n+  'cvsps' will also adapt without requiring a root or module\n+  specification when run from within a module subdirectory within a CVS\n+  repository directory.  When run from within the top level of a CVS\n+  repository, 'cvsps' requires only a module argument.\n+\n+* The -S option will interpret exclusion regular expressions using the\n+  POSIX syntax of regex(7).\n+\n+* 'cvsps' automatically removes characters in CVS tag and branch names\n+  that would be illegal in git.\n+\n+cvs2git\n+~~~~~~~\n+The cvs2git project page is at `http://cvs2svn.tigris.org`.  It is\n+much slower than cvsps, and does not implement some git-cvsimport\n+options (such as -d and -A), but it handles a wider range of\n+pathological CVS cases.\n+\n+* 'cvs2git' takes a path option pointing to a repository module\n+  subdirectory, defaulting to \".\".\n+\n+* The -S option will interpret exclusion regular expressions using\n+  Python syntax.\n+\n+* Illegal characters in branch and tag names will cause cvs2git to\n+  abort with an error message.\n \n GIT\n ---\ndiff --git a/Makefile b/Makefile\nindex 736ecd4..626494e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -464,7 +464,6 @@ SCRIPT_PERL += git-add--interactive.perl\n SCRIPT_PERL += git-difftool.perl\n SCRIPT_PERL += git-archimport.perl\n SCRIPT_PERL += git-cvsexportcommit.perl\n-SCRIPT_PERL += git-cvsimport.perl\n SCRIPT_PERL += git-cvsserver.perl\n SCRIPT_PERL += git-relink.perl\n SCRIPT_PERL += git-send-email.perl\n@@ -472,6 +471,7 @@ SCRIPT_PERL += git-svn.perl\n \n SCRIPT_PYTHON += git-remote-testgit.py\n SCRIPT_PYTHON += git-p4.py\n+SCRIPT_PYTHON += git-cvsimport.py\n \n SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \\\n \t  $(patsubst %.perl,%,$(SCRIPT_PERL)) \\\ndiff --git a/git-cvsimport.perl b/git-cvsimport.perl\ndeleted file mode 100755\nindex 0a31ebd..0000000\n--- a/git-cvsimport.perl\n+++ /dev/null\n@@ -1,1177 +0,0 @@\n-#!/usr/bin/perl\n-\n-# This tool is copyright (c) 2005, Matthias Urlichs.\n-# It is released under the Gnu Public License, version 2.\n-#\n-# The basic idea is to aggregate CVS check-ins into related changes.\n-# Fortunately, \"cvsps\" does that for us; all we have to do is to parse\n-# its output.\n-#\n-# Checking out the files is done by a single long-running CVS connection\n-# / server process.\n-#\n-# The head revision is on branch \"origin\" by default.\n-# You can change that with the '-o' option.\n-\n-use 5.008;\n-use strict;\n-use warnings;\n-use Getopt::Long;\n-use File::Spec;\n-use File::Temp qw(tempfile tmpnam);\n-use File::Path qw(mkpath);\n-use File::Basename qw(basename dirname);\n-use Time::Local;\n-use IO::Socket;\n-use IO::Pipe;\n-use POSIX qw(strftime tzset dup2 ENOENT);\n-use IPC::Open2;\n-\n-$SIG{'PIPE'}=\"IGNORE\";\n-set_timezone('UTC');\n-\n-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);\n-my (%conv_author_name, %conv_author_email, %conv_author_tz);\n-\n-sub usage(;$) {\n-\tmy $msg = shift;\n-\tprint(STDERR \"Error: $msg\\n\") if $msg;\n-\tprint STDERR <<END;\n-Usage: git cvsimport     # fetch/update GIT from CVS\n-       [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]\n-       [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]\n-       [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]\n-       [-r remote] [-R] [CVS_module]\n-END\n-\texit(1);\n-}\n-\n-sub read_author_info($) {\n-\tmy ($file) = @_;\n-\tmy $user;\n-\topen my $f, '<', \"$file\" or die(\"Failed to open $file: $!\\n\");\n-\n-\twhile (<$f>) {\n-\t\t# Expected format is this:\n-\t\t#   exon=Andreas Ericsson <ae@op5.se>\n-\t\tif (m/^(\\S+?)\\s*=\\s*(.+?)\\s*<(.+)>\\s*$/) {\n-\t\t\t$user = $1;\n-\t\t\t$conv_author_name{$user} = $2;\n-\t\t\t$conv_author_email{$user} = $3;\n-\t\t}\n-\t\t# or with an optional timezone:\n-\t\t#   spawn=Simon Pawn <spawn@frog-pond.org> America/Chicago\n-\t\telsif (m/^(\\S+?)\\s*=\\s*(.+?)\\s*<(.+)>\\s*(\\S+?)\\s*$/) {\n-\t\t\t$user = $1;\n-\t\t\t$conv_author_name{$user} = $2;\n-\t\t\t$conv_author_email{$user} = $3;\n-\t\t\t$conv_author_tz{$user} = $4;\n-\t\t}\n-\t\t# However, we also read from CVSROOT/users format\n-\t\t# to ease migration.\n-\t\telsif (/^(\\w+):(['\"]?)(.+?)\\2\\s*$/) {\n-\t\t\tmy $mapped;\n-\t\t\t($user, $mapped) = ($1, $3);\n-\t\t\tif ($mapped =~ /^\\s*(.*?)\\s*<(.*)>\\s*$/) {\n-\t\t\t\t$conv_author_name{$user} = $1;\n-\t\t\t\t$conv_author_email{$user} = $2;\n-\t\t\t}\n-\t\t\telsif ($mapped =~ /^<?(.*)>?$/) {\n-\t\t\t\t$conv_author_name{$user} = $user;\n-\t\t\t\t$conv_author_email{$user} = $1;\n-\t\t\t}\n-\t\t}\n-\t\t# NEEDSWORK: Maybe warn on unrecognized lines?\n-\t}\n-\tclose ($f);\n-}\n-\n-sub write_author_info($) {\n-\tmy ($file) = @_;\n-\topen my $f, '>', $file or\n-\t  die(\"Failed to open $file for writing: $!\");\n-\n-\tforeach (keys %conv_author_name) {\n-\t\tprint $f \"$_=$conv_author_name{$_} <$conv_author_email{$_}>\";\n-\t\tprint $f \" $conv_author_tz{$_}\" if ($conv_author_tz{$_});\n-\t\tprint $f \"\\n\";\n-\t}\n-\tclose ($f);\n-}\n-\n-# Versions of perl before 5.10.0 may not automatically check $TZ each\n-# time localtime is run (most platforms will do so only the first time).\n-# We can work around this by using tzset() to update the internal\n-# variable whenever we change the environment.\n-sub set_timezone {\n-\t$ENV{TZ} = shift;\n-\ttzset();\n-}\n-\n-# convert getopts specs for use by git config\n-my %longmap = (\n-\t'A:' => 'authors-file',\n-\t'M:' => 'merge-regex',\n-\t'P:' => undef,\n-\t'R' => 'track-revisions',\n-\t'S:' => 'ignore-paths',\n-);\n-\n-sub read_repo_config {\n-\t# Split the string between characters, unless there is a ':'\n-\t# So \"abc:de\" becomes [\"a\", \"b\", \"c:\", \"d\", \"e\"]\n-\tmy @opts = split(/ *(?!:)/, shift);\n-\tforeach my $o (@opts) {\n-\t\tmy $key = $o;\n-\t\t$key =~ s/://g;\n-\t\tmy $arg = 'git config';\n-\t\t$arg .= ' --bool' if ($o !~ /:$/);\n-\t\tmy $ckey = $key;\n-\n-\t\tif (exists $longmap{$o}) {\n-\t\t\t# An uppercase option like -R cannot be\n-\t\t\t# expressed in the configuration, as the\n-\t\t\t# variable names are downcased.\n-\t\t\t$ckey = $longmap{$o};\n-\t\t\tnext if (! defined $ckey);\n-\t\t\t$ckey =~ s/-//g;\n-\t\t}\n-\t\tchomp(my $tmp = `$arg --get cvsimport.$ckey`);\n-\t\tif ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {\n-\t\t\tno strict 'refs';\n-\t\t\tmy $opt_name = \"opt_\" . $key;\n-\t\t\tif (!$$opt_name) {\n-\t\t\t\t$$opt_name = $tmp;\n-\t\t\t}\n-\t\t}\n-\t}\n-}\n-\n-my $opts = \"haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R\";\n-read_repo_config($opts);\n-Getopt::Long::Configure( 'no_ignore_case', 'bundling' );\n-\n-# turn the Getopt::Std specification in a Getopt::Long one,\n-# with support for multiple -M options\n-GetOptions( map { s/:/=s/; /M/ ? \"$_\\@\" : $_ } split( /(?!:)/, $opts ) )\n-    or usage();\n-usage if $opt_h;\n-\n-if (@ARGV == 0) {\n-\t\tchomp(my $module = `git config --get cvsimport.module`);\n-\t\tpush(@ARGV, $module) if $? == 0;\n-}\n-@ARGV <= 1 or usage(\"You can't specify more than one CVS module\");\n-\n-if ($opt_d) {\n-\t$ENV{\"CVSROOT\"} = $opt_d;\n-} elsif (-f 'CVS/Root') {\n-\topen my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';\n-\t$opt_d = <$f>;\n-\tchomp $opt_d;\n-\tclose $f;\n-\t$ENV{\"CVSROOT\"} = $opt_d;\n-} elsif ($ENV{\"CVSROOT\"}) {\n-\t$opt_d = $ENV{\"CVSROOT\"};\n-} else {\n-\tusage(\"CVSROOT needs to be set\");\n-}\n-$opt_s ||= \"-\";\n-$opt_a ||= 0;\n-\n-my $git_tree = $opt_C;\n-$git_tree ||= \".\";\n-\n-my $remote;\n-if (defined $opt_r) {\n-\t$remote = 'refs/remotes/' . $opt_r;\n-\t$opt_o ||= \"master\";\n-} else {\n-\t$opt_o ||= \"origin\";\n-\t$remote = 'refs/heads';\n-}\n-\n-my $cvs_tree;\n-if ($#ARGV == 0) {\n-\t$cvs_tree = $ARGV[0];\n-} elsif (-f 'CVS/Repository') {\n-\topen my $f, '<', 'CVS/Repository' or\n-\t    die 'Failed to open CVS/Repository';\n-\t$cvs_tree = <$f>;\n-\tchomp $cvs_tree;\n-\tclose $f;\n-} else {\n-\tusage(\"CVS module has to be specified\");\n-}\n-\n-our @mergerx = ();\n-if ($opt_m) {\n-\t@mergerx = ( qr/\\b(?:from|of|merge|merging|merged) ([-\\w]+)/i );\n-}\n-if (@opt_M) {\n-\tpush (@mergerx, map { qr/$_/ } @opt_M);\n-}\n-\n-# Remember UTC of our starting time\n-# we'll want to avoid importing commits\n-# that are too recent\n-our $starttime = time();\n-\n-select(STDERR); $|=1; select(STDOUT);\n-\n-\n-package CVSconn;\n-# Basic CVS dialog.\n-# We're only interested in connecting and downloading, so ...\n-\n-use File::Spec;\n-use File::Temp qw(tempfile);\n-use POSIX qw(strftime dup2);\n-\n-sub new {\n-\tmy ($what,$repo,$subdir) = @_;\n-\t$what=ref($what) if ref($what);\n-\n-\tmy $self = {};\n-\t$self->{'buffer'} = \"\";\n-\tbless($self,$what);\n-\n-\t$repo =~ s#/+$##;\n-\t$self->{'fullrep'} = $repo;\n-\t$self->conn();\n-\n-\t$self->{'subdir'} = $subdir;\n-\t$self->{'lines'} = undef;\n-\n-\treturn $self;\n-}\n-\n-sub find_password_entry {\n-\tmy ($cvspass, @cvsroot) = @_;\n-\tmy ($file, $delim) = @$cvspass;\n-\tmy $pass;\n-\tlocal ($_);\n-\n-\tif (open(my $fh, $file)) {\n-\t\t# :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z\n-\t\tCVSPASSFILE:\n-\t\twhile (<$fh>) {\n-\t\t\tchomp;\n-\t\t\ts/^\\/\\d+\\s+//;\n-\t\t\tmy ($w, $p) = split($delim,$_,2);\n-\t\t\tfor my $cvsroot (@cvsroot) {\n-\t\t\t\tif ($w eq $cvsroot) {\n-\t\t\t\t\t$pass = $p;\n-\t\t\t\t\tlast CVSPASSFILE;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t}\n-\t\tclose($fh);\n-\t}\n-\treturn $pass;\n-}\n-\n-sub conn {\n-\tmy $self = shift;\n-\tmy $repo = $self->{'fullrep'};\n-\tif ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\\/]*)(?::(\\d*))?//) {\n-\t\tmy ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);\n-\n-\t\tmy ($proxyhost,$proxyport);\n-\t\tif ($param && ($param =~ m/proxy=([^;]+)/)) {\n-\t\t\t$proxyhost = $1;\n-\t\t\t# Default proxyport, if not specified, is 8080.\n-\t\t\t$proxyport = 8080;\n-\t\t\tif ($ENV{\"CVS_PROXY_PORT\"}) {\n-\t\t\t\t$proxyport = $ENV{\"CVS_PROXY_PORT\"};\n-\t\t\t}\n-\t\t\tif ($param =~ m/proxyport=([^;]+)/) {\n-\t\t\t\t$proxyport = $1;\n-\t\t\t}\n-\t\t}\n-\t\t$repo ||= '/';\n-\n-\t\t# if username is not explicit in CVSROOT, then use current user, as cvs would\n-\t\t$user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || \"anonymous\") unless $user;\n-\t\tmy $rr2 = \"-\";\n-\t\tunless ($port) {\n-\t\t\t$rr2 = \":pserver:$user\\@$serv:$repo\";\n-\t\t\t$port=2401;\n-\t\t}\n-\t\tmy $rr = \":pserver:$user\\@$serv:$port$repo\";\n-\n-\t\tif ($pass) {\n-\t\t\t$pass = $self->_scramble($pass);\n-\t\t} else {\n-\t\t\tmy @cvspass = ([$ENV{'HOME'}.\"/.cvspass\", qr/\\s/],\n-\t\t\t\t       [$ENV{'HOME'}.\"/.cvs/cvspass\", qr/=/]);\n-\t\t\tmy @loc = ();\n-\t\t\tforeach my $cvspass (@cvspass) {\n-\t\t\t\tmy $p = find_password_entry($cvspass, $rr, $rr2);\n-\t\t\t\tif ($p) {\n-\t\t\t\t\tpush @loc, $cvspass->[0];\n-\t\t\t\t\t$pass = $p;\n-\t\t\t\t}\n-\t\t\t}\n-\n-\t\t\tif (1 < @loc) {\n-\t\t\t\tdie(\"Multiple cvs password files have \".\n-\t\t\t\t    \"entries for CVSROOT $opt_d: @loc\");\n-\t\t\t} elsif (!$pass) {\n-\t\t\t\t$pass = \"A\";\n-\t\t\t}\n-\t\t}\n-\n-\t\tmy ($s, $rep);\n-\t\tif ($proxyhost) {\n-\n-\t\t\t# Use a HTTP Proxy. Only works for HTTP proxies that\n-\t\t\t# don't require user authentication\n-\t\t\t#\n-\t\t\t# See: http://www.ietf.org/rfc/rfc2817.txt\n-\n-\t\t\t$s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);\n-\t\t\tdie \"Socket to $proxyhost: $!\\n\" unless defined $s;\n-\t\t\t$s->write(\"CONNECT $serv:$port HTTP/1.1\\r\\nHost: $serv:$port\\r\\n\\r\\n\")\n-\t                        or die \"Write to $proxyhost: $!\\n\";\n-\t                $s->flush();\n-\n-\t\t\t$rep = <$s>;\n-\n-\t\t\t# The answer should look like 'HTTP/1.x 2yy ....'\n-\t\t\tif (!($rep =~ m#^HTTP/1\\.. 2[0-9][0-9]#)) {\n-\t\t\t\tdie \"Proxy connect: $rep\\n\";\n-\t\t\t}\n-\t\t\t# Skip up to the empty line of the proxy server output\n-\t\t\t# including the response headers.\n-\t\t\twhile ($rep = <$s>) {\n-\t\t\t\tlast if (!defined $rep ||\n-\t\t\t\t\t $rep eq \"\\n\" ||\n-\t\t\t\t\t $rep eq \"\\r\\n\");\n-\t\t\t}\n-\t\t} else {\n-\t\t\t$s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);\n-\t\t\tdie \"Socket to $serv: $!\\n\" unless defined $s;\n-\t\t}\n-\n-\t\t$s->write(\"BEGIN AUTH REQUEST\\n$repo\\n$user\\n$pass\\nEND AUTH REQUEST\\n\")\n-\t\t\tor die \"Write to $serv: $!\\n\";\n-\t\t$s->flush();\n-\n-\t\t$rep = <$s>;\n-\n-\t\tif ($rep ne \"I LOVE YOU\\n\") {\n-\t\t\t$rep=\"<unknown>\" unless $rep;\n-\t\t\tdie \"AuthReply: $rep\\n\";\n-\t\t}\n-\t\t$self->{'socketo'} = $s;\n-\t\t$self->{'socketi'} = $s;\n-\t} else { # local or ext: Fork off our own cvs server.\n-\t\tmy $pr = IO::Pipe->new();\n-\t\tmy $pw = IO::Pipe->new();\n-\t\tmy $pid = fork();\n-\t\tdie \"Fork: $!\\n\" unless defined $pid;\n-\t\tmy $cvs = 'cvs';\n-\t\t$cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};\n-\t\tmy $rsh = 'rsh';\n-\t\t$rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};\n-\n-\t\tmy @cvs = ($cvs, 'server');\n-\t\tmy ($local, $user, $host);\n-\t\t$local = $repo =~ s/:local://;\n-\t\tif (!$local) {\n-\t\t    $repo =~ s/:ext://;\n-\t\t    $local = !($repo =~ s/^(?:([^\\@:]+)\\@)?([^:]+)://);\n-\t\t    ($user, $host) = ($1, $2);\n-\t\t}\n-\t\tif (!$local) {\n-\t\t    if ($user) {\n-\t\t\tunshift @cvs, $rsh, '-l', $user, $host;\n-\t\t    } else {\n-\t\t\tunshift @cvs, $rsh, $host;\n-\t\t    }\n-\t\t}\n-\n-\t\tunless ($pid) {\n-\t\t\t$pr->writer();\n-\t\t\t$pw->reader();\n-\t\t\tdup2($pw->fileno(),0);\n-\t\t\tdup2($pr->fileno(),1);\n-\t\t\t$pr->close();\n-\t\t\t$pw->close();\n-\t\t\texec(@cvs);\n-\t\t}\n-\t\t$pw->writer();\n-\t\t$pr->reader();\n-\t\t$self->{'socketo'} = $pw;\n-\t\t$self->{'socketi'} = $pr;\n-\t}\n-\t$self->{'socketo'}->write(\"Root $repo\\n\");\n-\n-\t# Trial and error says that this probably is the minimum set\n-\t$self->{'socketo'}->write(\"Valid-responses ok error Valid-requests Mode M Mbinary E Checked-in Created Updated Merged Removed\\n\");\n-\n-\t$self->{'socketo'}->write(\"valid-requests\\n\");\n-\t$self->{'socketo'}->flush();\n-\n-\tmy $rep=$self->readline();\n-\tdie \"Failed to read from server\" unless defined $rep;\n-\tchomp($rep);\n-\tif ($rep !~ s/^Valid-requests\\s*//) {\n-\t\t$rep=\"<unknown>\" unless $rep;\n-\t\tdie \"Expected Valid-requests from server, but got: $rep\\n\";\n-\t}\n-\tchomp(my $res=$self->readline());\n-\tdie \"validReply: $res\\n\" if $res ne \"ok\";\n-\n-\t$self->{'socketo'}->write(\"UseUnchanged\\n\") if $rep =~ /\\bUseUnchanged\\b/;\n-\t$self->{'repo'} = $repo;\n-}\n-\n-sub readline {\n-\tmy ($self) = @_;\n-\treturn $self->{'socketi'}->getline();\n-}\n-\n-sub _file {\n-\t# Request a file with a given revision.\n-\t# Trial and error says this is a good way to do it. :-/\n-\tmy ($self,$fn,$rev) = @_;\n-\t$self->{'socketo'}->write(\"Argument -N\\n\") or return undef;\n-\t$self->{'socketo'}->write(\"Argument -P\\n\") or return undef;\n-\t# -kk: Linus' version doesn't use it - defaults to off\n-\tif ($opt_k) {\n-\t    $self->{'socketo'}->write(\"Argument -kk\\n\") or return undef;\n-\t}\n-\t$self->{'socketo'}->write(\"Argument -r\\n\") or return undef;\n-\t$self->{'socketo'}->write(\"Argument $rev\\n\") or return undef;\n-\t$self->{'socketo'}->write(\"Argument --\\n\") or return undef;\n-\t$self->{'socketo'}->write(\"Argument $self->{'subdir'}/$fn\\n\") or return undef;\n-\t$self->{'socketo'}->write(\"Directory .\\n\") or return undef;\n-\t$self->{'socketo'}->write(\"$self->{'repo'}\\n\") or return undef;\n-\t# $self->{'socketo'}->write(\"Sticky T1.0\\n\") or return undef;\n-\t$self->{'socketo'}->write(\"co\\n\") or return undef;\n-\t$self->{'socketo'}->flush() or return undef;\n-\t$self->{'lines'} = 0;\n-\treturn 1;\n-}\n-sub _line {\n-\t# Read a line from the server.\n-\t# ... except that 'line' may be an entire file. ;-)\n-\tmy ($self, $fh) = @_;\n-\tdie \"Not in lines\" unless defined $self->{'lines'};\n-\n-\tmy $line;\n-\tmy $res=0;\n-\twhile (defined($line = $self->readline())) {\n-\t\t# M U gnupg-cvs-rep/AUTHORS\n-\t\t# Updated gnupg-cvs-rep/\n-\t\t# /daten/src/rsync/gnupg-cvs-rep/AUTHORS\n-\t\t# /AUTHORS/1.1///T1.1\n-\t\t# u=rw,g=rw,o=rw\n-\t\t# 0\n-\t\t# ok\n-\n-\t\tif ($line =~ s/^(?:Created|Updated) //) {\n-\t\t\t$line = $self->readline(); # path\n-\t\t\t$line = $self->readline(); # Entries line\n-\t\t\tmy $mode = $self->readline(); chomp $mode;\n-\t\t\t$self->{'mode'} = $mode;\n-\t\t\tdefined (my $cnt = $self->readline())\n-\t\t\t\tor die \"EOF from server after 'Changed'\\n\";\n-\t\t\tchomp $cnt;\n-\t\t\tdie \"Duh: Filesize $cnt\" if $cnt !~ /^\\d+$/;\n-\t\t\t$line=\"\";\n-\t\t\t$res = $self->_fetchfile($fh, $cnt);\n-\t\t} elsif ($line =~ s/^ //) {\n-\t\t\tprint $fh $line;\n-\t\t\t$res += length($line);\n-\t\t} elsif ($line =~ /^M\\b/) {\n-\t\t\t# output, do nothing\n-\t\t} elsif ($line =~ /^Mbinary\\b/) {\n-\t\t\tmy $cnt;\n-\t\t\tdie \"EOF from server after 'Mbinary'\" unless defined ($cnt = $self->readline());\n-\t\t\tchomp $cnt;\n-\t\t\tdie \"Duh: Mbinary $cnt\" if $cnt !~ /^\\d+$/ or $cnt<1;\n-\t\t\t$line=\"\";\n-\t\t\t$res += $self->_fetchfile($fh, $cnt);\n-\t\t} else {\n-\t\t\tchomp $line;\n-\t\t\tif ($line eq \"ok\") {\n-\t\t\t\t# print STDERR \"S: ok (\".length($res).\")\\n\";\n-\t\t\t\treturn $res;\n-\t\t\t} elsif ($line =~ s/^E //) {\n-\t\t\t\t# print STDERR \"S: $line\\n\";\n-\t\t\t} elsif ($line =~ /^(Remove-entry|Removed) /i) {\n-\t\t\t\t$line = $self->readline(); # filename\n-\t\t\t\t$line = $self->readline(); # OK\n-\t\t\t\tchomp $line;\n-\t\t\t\tdie \"Unknown: $line\" if $line ne \"ok\";\n-\t\t\t\treturn -1;\n-\t\t\t} else {\n-\t\t\t\tdie \"Unknown: $line\\n\";\n-\t\t\t}\n-\t\t}\n-\t}\n-\treturn undef;\n-}\n-sub file {\n-\tmy ($self,$fn,$rev) = @_;\n-\tmy $res;\n-\n-\tmy ($fh, $name) = tempfile('gitcvs.XXXXXX',\n-\t\t    DIR => File::Spec->tmpdir(), UNLINK => 1);\n-\n-\t$self->_file($fn,$rev) and $res = $self->_line($fh);\n-\n-\tif (!defined $res) {\n-\t    print STDERR \"Server has gone away while fetching $fn $rev, retrying...\\n\";\n-\t    truncate $fh, 0;\n-\t    $self->conn();\n-\t    $self->_file($fn,$rev) or die \"No file command send\";\n-\t    $res = $self->_line($fh);\n-\t    die \"Retry failed\" unless defined $res;\n-\t}\n-\tclose ($fh);\n-\n-\treturn ($name, $res);\n-}\n-sub _fetchfile {\n-\tmy ($self, $fh, $cnt) = @_;\n-\tmy $res = 0;\n-\tmy $bufsize = 1024 * 1024;\n-\twhile ($cnt) {\n-\t    if ($bufsize > $cnt) {\n-\t\t$bufsize = $cnt;\n-\t    }\n-\t    my $buf;\n-\t    my $num = $self->{'socketi'}->read($buf,$bufsize);\n-\t    die \"Server: Filesize $cnt: $num: $!\\n\" if not defined $num or $num<=0;\n-\t    print $fh $buf;\n-\t    $res += $num;\n-\t    $cnt -= $num;\n-\t}\n-\treturn $res;\n-}\n-\n-sub _scramble {\n-\tmy ($self, $pass) = @_;\n-\tmy $scrambled = \"A\";\n-\n-\treturn $scrambled unless $pass;\n-\n-\tmy $pass_len = length($pass);\n-\tmy @pass_arr = split(\"\", $pass);\n-\tmy $i;\n-\n-\t# from cvs/src/scramble.c\n-\tmy @shifts = (\n-\t\t  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,\n-\t\t 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n-\t\t114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,\n-\t\t111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,\n-\t\t 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,\n-\t\t125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,\n-\t\t 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,\n-\t\t 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,\n-\t\t225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,\n-\t\t199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,\n-\t\t174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,\n-\t\t207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,\n-\t\t192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,\n-\t\t227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,\n-\t\t182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,\n-\t\t243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152\n-\t);\n-\n-\tfor ($i = 0; $i < $pass_len; $i++) {\n-\t\t$scrambled .= pack(\"C\", $shifts[ord($pass_arr[$i])]);\n-\t}\n-\n-\treturn $scrambled;\n-}\n-\n-package main;\n-\n-my $cvs = CVSconn->new($opt_d, $cvs_tree);\n-\n-\n-sub pdate($) {\n-\tmy ($d) = @_;\n-\tm#(\\d{2,4})/(\\d\\d)/(\\d\\d)\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?#\n-\t\tor die \"Unparseable date: $d\\n\";\n-\tmy $y=$1; $y-=1900 if $y>1900;\n-\treturn timegm($6||0,$5,$4,$3,$2-1,$y);\n-}\n-\n-sub pmode($) {\n-\tmy ($mode) = @_;\n-\tmy $m = 0;\n-\tmy $mm = 0;\n-\tmy $um = 0;\n-\tfor my $x(split(//,$mode)) {\n-\t\tif ($x eq \",\") {\n-\t\t\t$m |= $mm&$um;\n-\t\t\t$mm = 0;\n-\t\t\t$um = 0;\n-\t\t} elsif ($x eq \"u\") { $um |= 0700;\n-\t\t} elsif ($x eq \"g\") { $um |= 0070;\n-\t\t} elsif ($x eq \"o\") { $um |= 0007;\n-\t\t} elsif ($x eq \"r\") { $mm |= 0444;\n-\t\t} elsif ($x eq \"w\") { $mm |= 0222;\n-\t\t} elsif ($x eq \"x\") { $mm |= 0111;\n-\t\t} elsif ($x eq \"=\") { # do nothing\n-\t\t} else { die \"Unknown mode: $mode\\n\";\n-\t\t}\n-\t}\n-\t$m |= $mm&$um;\n-\treturn $m;\n-}\n-\n-sub getwd() {\n-\tmy $pwd = `pwd`;\n-\tchomp $pwd;\n-\treturn $pwd;\n-}\n-\n-sub is_sha1 {\n-\tmy $s = shift;\n-\treturn $s =~ /^[a-f0-9]{40}$/;\n-}\n-\n-sub get_headref ($) {\n-\tmy $name = shift;\n-\tmy $r = `git rev-parse --verify '$name' 2>/dev/null`;\n-\treturn undef unless $? == 0;\n-\tchomp $r;\n-\treturn $r;\n-}\n-\n-my $user_filename_prepend = '';\n-sub munge_user_filename {\n-\tmy $name = shift;\n-\treturn File::Spec->file_name_is_absolute($name) ?\n-\t\t$name :\n-\t\t$user_filename_prepend . $name;\n-}\n-\n--d $git_tree\n-\tor mkdir($git_tree,0777)\n-\tor die \"Could not create $git_tree: $!\";\n-if ($git_tree ne '.') {\n-\t$user_filename_prepend = getwd() . '/';\n-\tchdir($git_tree);\n-}\n-\n-my $last_branch = \"\";\n-my $orig_branch = \"\";\n-my %branch_date;\n-my $tip_at_start = undef;\n-\n-my $git_dir = $ENV{\"GIT_DIR\"} || \".git\";\n-$git_dir = getwd().\"/\".$git_dir unless $git_dir =~ m#^/#;\n-$ENV{\"GIT_DIR\"} = $git_dir;\n-my $orig_git_index;\n-$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};\n-\n-my %index; # holds filenames of one index per branch\n-\n-unless (-d $git_dir) {\n-\tsystem(qw(git init));\n-\tdie \"Cannot init the GIT db at $git_tree: $?\\n\" if $?;\n-\tsystem(qw(git read-tree --empty));\n-\tdie \"Cannot init an empty tree: $?\\n\" if $?;\n-\n-\t$last_branch = $opt_o;\n-\t$orig_branch = \"\";\n-} else {\n-\topen(F, \"-|\", qw(git symbolic-ref HEAD)) or\n-\t\tdie \"Cannot run git symbolic-ref: $!\\n\";\n-\tchomp ($last_branch = <F>);\n-\t$last_branch = basename($last_branch);\n-\tclose(F);\n-\tunless ($last_branch) {\n-\t\twarn \"Cannot read the last branch name: $! -- assuming 'master'\\n\";\n-\t\t$last_branch = \"master\";\n-\t}\n-\t$orig_branch = $last_branch;\n-\t$tip_at_start = `git rev-parse --verify HEAD`;\n-\n-\t# Get the last import timestamps\n-\tmy $fmt = '($ref, $author) = (%(refname), %(author));';\n-\tmy @cmd = ('git', 'for-each-ref', '--perl', \"--format=$fmt\", $remote);\n-\topen(H, \"-|\", @cmd) or die \"Cannot run git for-each-ref: $!\\n\";\n-\twhile (defined(my $entry = <H>)) {\n-\t\tmy ($ref, $author);\n-\t\teval($entry) || die \"cannot eval refs list: $@\";\n-\t\tmy ($head) = ($ref =~ m|^$remote/(.*)|);\n-\t\t$author =~ /^.*\\s(\\d+)\\s[-+]\\d{4}$/;\n-\t\t$branch_date{$head} = $1;\n-\t}\n-\tclose(H);\n-        if (!exists $branch_date{$opt_o}) {\n-\t\tdie \"Branch '$opt_o' does not exist.\\n\".\n-\t\t       \"Either use the correct '-o branch' option,\\n\".\n-\t\t       \"or import to a new repository.\\n\";\n-        }\n-}\n-\n--d $git_dir\n-\tor die \"Could not create git subdir ($git_dir).\\n\";\n-\n-# now we read (and possibly save) author-info as well\n--f \"$git_dir/cvs-authors\" and\n-  read_author_info(\"$git_dir/cvs-authors\");\n-if ($opt_A) {\n-\tread_author_info(munge_user_filename($opt_A));\n-\twrite_author_info(\"$git_dir/cvs-authors\");\n-}\n-\n-# open .git/cvs-revisions, if requested\n-open my $revision_map, '>>', \"$git_dir/cvs-revisions\"\n-    or die \"Can't open $git_dir/cvs-revisions for appending: $!\\n\"\n-\tif defined $opt_R;\n-\n-\n-#\n-# run cvsps into a file unless we are getting\n-# it passed as a file via $opt_P\n-#\n-my $cvspsfile;\n-unless ($opt_P) {\n-\tprint \"Running cvsps...\\n\" if $opt_v;\n-\tmy $pid = open(CVSPS,\"-|\");\n-\tmy $cvspsfh;\n-\tdie \"Cannot fork: $!\\n\" unless defined $pid;\n-\tunless ($pid) {\n-\t\tmy @opt;\n-\t\t@opt = split(/,/,$opt_p) if defined $opt_p;\n-\t\tunshift @opt, '-z', $opt_z if defined $opt_z;\n-\t\tunshift @opt, '-q'         unless defined $opt_v;\n-\t\tunless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {\n-\t\t\tpush @opt, '--cvs-direct';\n-\t\t}\n-\t\texec(\"cvsps\",\"--norc\",@opt,\"-u\",\"-A\",'--root',$opt_d,$cvs_tree);\n-\t\tdie \"Could not start cvsps: $!\\n\";\n-\t}\n-\t($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',\n-\t\t\t\t\t  DIR => File::Spec->tmpdir());\n-\twhile (<CVSPS>) {\n-\t    print $cvspsfh $_;\n-\t}\n-\tclose CVSPS;\n-\t$? == 0 or die \"git cvsimport: fatal: cvsps reported error\\n\";\n-\tclose $cvspsfh;\n-} else {\n-\t$cvspsfile = munge_user_filename($opt_P);\n-}\n-\n-open(CVS, \"<$cvspsfile\") or die $!;\n-\n-## cvsps output:\n-#---------------------\n-#PatchSet 314\n-#Date: 1999/09/18 13:03:59\n-#Author: wkoch\n-#Branch: STABLE-BRANCH-1-0\n-#Ancestor branch: HEAD\n-#Tag: (none)\n-#Log:\n-#    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch\n-#Members:\n-#\tREADME:1.57->1.57.2.1\n-#\tVERSION:1.96->1.96.2.1\n-#\n-#---------------------\n-\n-my $state = 0;\n-\n-sub update_index (\\@\\@) {\n-\tmy $old = shift;\n-\tmy $new = shift;\n-\topen(my $fh, '|-', qw(git update-index -z --index-info))\n-\t\tor die \"unable to open git update-index: $!\";\n-\tprint $fh\n-\t\t(map { \"0 0000000000000000000000000000000000000000\\t$_\\0\" }\n-\t\t\t@$old),\n-\t\t(map { '100' . sprintf('%o', $_->[0]) . \" $_->[1]\\t$_->[2]\\0\" }\n-\t\t\t@$new)\n-\t\tor die \"unable to write to git update-index: $!\";\n-\tclose $fh\n-\t\tor die \"unable to write to git update-index: $!\";\n-\t$? and die \"git update-index reported error: $?\";\n-}\n-\n-sub write_tree () {\n-\topen(my $fh, '-|', qw(git write-tree))\n-\t\tor die \"unable to open git write-tree: $!\";\n-\tchomp(my $tree = <$fh>);\n-\tis_sha1($tree)\n-\t\tor die \"Cannot get tree id ($tree): $!\";\n-\tclose($fh)\n-\t\tor die \"Error running git write-tree: $?\\n\";\n-\tprint \"Tree ID $tree\\n\" if $opt_v;\n-\treturn $tree;\n-}\n-\n-my ($patchset,$date,$author_name,$author_email,$author_tz,$branch,$ancestor,$tag,$logmsg);\n-my (@old,@new,@skipped,%ignorebranch,@commit_revisions);\n-\n-# commits that cvsps cannot place anywhere...\n-$ignorebranch{'#CVSPS_NO_BRANCH'} = 1;\n-\n-sub commit {\n-\tif ($branch eq $opt_o && !$index{branch} &&\n-\t\t!get_headref(\"$remote/$branch\")) {\n-\t    # looks like an initial commit\n-\t    # use the index primed by git init\n-\t    $ENV{GIT_INDEX_FILE} = \"$git_dir/index\";\n-\t    $index{$branch} = \"$git_dir/index\";\n-\t} else {\n-\t    # use an index per branch to speed up\n-\t    # imports of projects with many branches\n-\t    unless ($index{$branch}) {\n-\t\t$index{$branch} = tmpnam();\n-\t\t$ENV{GIT_INDEX_FILE} = $index{$branch};\n-\t\tif ($ancestor) {\n-\t\t    system(\"git\", \"read-tree\", \"$remote/$ancestor\");\n-\t\t} else {\n-\t\t    system(\"git\", \"read-tree\", \"$remote/$branch\");\n-\t\t}\n-\t\tdie \"read-tree failed: $?\\n\" if $?;\n-\t    }\n-\t}\n-        $ENV{GIT_INDEX_FILE} = $index{$branch};\n-\n-\tupdate_index(@old, @new);\n-\t@old = @new = ();\n-\tmy $tree = write_tree();\n-\tmy $parent = get_headref(\"$remote/$last_branch\");\n-\tprint \"Parent ID \" . ($parent ? $parent : \"(empty)\") . \"\\n\" if $opt_v;\n-\n-\tmy @commit_args;\n-\tpush @commit_args, (\"-p\", $parent) if $parent;\n-\n-\t# loose detection of merges\n-\t# based on the commit msg\n-\tforeach my $rx (@mergerx) {\n-\t\tnext unless $logmsg =~ $rx && $1;\n-\t\tmy $mparent = $1 eq 'HEAD' ? $opt_o : $1;\n-\t\tif (my $sha1 = get_headref(\"$remote/$mparent\")) {\n-\t\t\tpush @commit_args, '-p', \"$remote/$mparent\";\n-\t\t\tprint \"Merge parent branch: $mparent\\n\" if $opt_v;\n-\t\t}\n-\t}\n-\n-\tset_timezone($author_tz);\n-\tmy $commit_date = strftime(\"%s %z\", localtime($date));\n-\tset_timezone('UTC');\n-\t$ENV{GIT_AUTHOR_NAME} = $author_name;\n-\t$ENV{GIT_AUTHOR_EMAIL} = $author_email;\n-\t$ENV{GIT_AUTHOR_DATE} = $commit_date;\n-\t$ENV{GIT_COMMITTER_NAME} = $author_name;\n-\t$ENV{GIT_COMMITTER_EMAIL} = $author_email;\n-\t$ENV{GIT_COMMITTER_DATE} = $commit_date;\n-\tmy $pid = open2(my $commit_read, my $commit_write,\n-\t\t'git', 'commit-tree', $tree, @commit_args);\n-\n-\t# compatibility with git2cvs\n-\tsubstr($logmsg,32767) = \"\" if length($logmsg) > 32767;\n-\t$logmsg =~ s/[\\s\\n]+\\z//;\n-\n-\tif (@skipped) {\n-\t    $logmsg .= \"\\n\\n\\nSKIPPED:\\n\\t\";\n-\t    $logmsg .= join(\"\\n\\t\", @skipped) . \"\\n\";\n-\t    @skipped = ();\n-\t}\n-\n-\tprint($commit_write \"$logmsg\\n\") && close($commit_write)\n-\t\tor die \"Error writing to git commit-tree: $!\\n\";\n-\n-\tprint \"Committed patch $patchset ($branch $commit_date)\\n\" if $opt_v;\n-\tchomp(my $cid = <$commit_read>);\n-\tis_sha1($cid) or die \"Cannot get commit id ($cid): $!\\n\";\n-\tprint \"Commit ID $cid\\n\" if $opt_v;\n-\tclose($commit_read);\n-\n-\twaitpid($pid,0);\n-\tdie \"Error running git commit-tree: $?\\n\" if $?;\n-\n-\tsystem('git' , 'update-ref', \"$remote/$branch\", $cid) == 0\n-\t\tor die \"Cannot write branch $branch for update: $!\\n\";\n-\n-\tif ($revision_map) {\n-\t\tprint $revision_map \"@$_ $cid\\n\" for @commit_revisions;\n-\t}\n-\t@commit_revisions = ();\n-\n-\tif ($tag) {\n-\t        my ($xtag) = $tag;\n-\t\t$xtag =~ s/\\s+\\*\\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **\n-\t\t$xtag =~ tr/_/\\./ if ( $opt_u );\n-\t\t$xtag =~ s/[\\/]/$opt_s/g;\n-\n-\t\t# See refs.c for these rules.\n-\t\t# Tag cannot contain bad chars. (See bad_ref_char in refs.c.)\n-\t\t$xtag =~ s/[ ~\\^:\\\\\\*\\?\\[]//g;\n-\t\t# Other bad strings for tags:\n-\t\t# (See check_refname_component in refs.c.)\n-\t\t1 while $xtag =~ s/\n-\t\t\t(?: \\.\\.        # Tag cannot contain '..'.\n-\t\t\t|   \\@{         # Tag cannot contain '@{'.\n-\t\t\t| ^ -           # Tag cannot begin with '-'.\n-\t\t\t|   \\.lock $    # Tag cannot end with '.lock'.\n-\t\t\t| ^ \\.          # Tag cannot begin...\n-\t\t\t|   \\. $        # ...or end with '.'\n-\t\t\t)//xg;\n-\t\t# Tag cannot be empty.\n-\t\tif ($xtag eq '') {\n-\t\t\twarn(\"warning: ignoring tag '$tag'\",\n-\t\t\t\" with invalid tagname\\n\");\n-\t\t\treturn;\n-\t\t}\n-\n-\t\tif (system('git' , 'tag', '-f', $xtag, $cid) != 0) {\n-\t\t\t# We did our best to sanitize the tag, but still failed\n-\t\t\t# for whatever reason. Bail out, and give the user\n-\t\t\t# enough information to understand if/how we should\n-\t\t\t# improve the translation in the future.\n-\t\t\tif ($tag ne $xtag) {\n-\t\t\t\tprint \"Translated '$tag' tag to '$xtag'\\n\";\n-\t\t\t}\n-\t\t\tdie \"Cannot create tag $xtag: $!\\n\";\n-\t\t}\n-\n-\t\tprint \"Created tag '$xtag' on '$branch'\\n\" if $opt_v;\n-\t}\n-};\n-\n-my $commitcount = 1;\n-while (<CVS>) {\n-\tchomp;\n-\tif ($state == 0 and /^-+$/) {\n-\t\t$state = 1;\n-\t} elsif ($state == 0) {\n-\t\t$state = 1;\n-\t\tredo;\n-\t} elsif (($state==0 or $state==1) and s/^PatchSet\\s+//) {\n-\t\t$patchset = 0+$_;\n-\t\t$state=2;\n-\t} elsif ($state == 2 and s/^Date:\\s+//) {\n-\t\t$date = pdate($_);\n-\t\tunless ($date) {\n-\t\t\tprint STDERR \"Could not parse date: $_\\n\";\n-\t\t\t$state=0;\n-\t\t\tnext;\n-\t\t}\n-\t\t$state=3;\n-\t} elsif ($state == 3 and s/^Author:\\s+//) {\n-\t\t$author_tz = \"UTC\";\n-\t\ts/\\s+$//;\n-\t\tif (/^(.*?)\\s+<(.*)>/) {\n-\t\t    ($author_name, $author_email) = ($1, $2);\n-\t\t} elsif ($conv_author_name{$_}) {\n-\t\t\t$author_name = $conv_author_name{$_};\n-\t\t\t$author_email = $conv_author_email{$_};\n-\t\t\t$author_tz = $conv_author_tz{$_} if ($conv_author_tz{$_});\n-\t\t} else {\n-\t\t    $author_name = $author_email = $_;\n-\t\t}\n-\t\t$state = 4;\n-\t} elsif ($state == 4 and s/^Branch:\\s+//) {\n-\t\ts/\\s+$//;\n-\t\ttr/_/\\./ if ( $opt_u );\n-\t\ts/[\\/]/$opt_s/g;\n-\t\t$branch = $_;\n-\t\t$state = 5;\n-\t} elsif ($state == 5 and s/^Ancestor branch:\\s+//) {\n-\t\ts/\\s+$//;\n-\t\t$ancestor = $_;\n-\t\t$ancestor = $opt_o if $ancestor eq \"HEAD\";\n-\t\t$state = 6;\n-\t} elsif ($state == 5) {\n-\t\t$ancestor = undef;\n-\t\t$state = 6;\n-\t\tredo;\n-\t} elsif ($state == 6 and s/^Tag:\\s+//) {\n-\t\ts/\\s+$//;\n-\t\tif ($_ eq \"(none)\") {\n-\t\t\t$tag = undef;\n-\t\t} else {\n-\t\t\t$tag = $_;\n-\t\t}\n-\t\t$state = 7;\n-\t} elsif ($state == 7 and /^Log:/) {\n-\t\t$logmsg = \"\";\n-\t\t$state = 8;\n-\t} elsif ($state == 8 and /^Members:/) {\n-\t\t$branch = $opt_o if $branch eq \"HEAD\";\n-\t\tif (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {\n-\t\t\t# skip\n-\t\t\tprint \"skip patchset $patchset: $date before $branch_date{$branch}\\n\" if $opt_v;\n-\t\t\t$state = 11;\n-\t\t\tnext;\n-\t\t}\n-\t\tif (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {\n-\t\t\t# skip if the commit is too recent\n-\t\t\t# given that the cvsps default fuzz is 300s, we give ourselves another\n-\t\t\t# 300s just in case -- this also prevents skipping commits\n-\t\t\t# due to server clock drift\n-\t\t\tprint \"skip patchset $patchset: $date too recent\\n\" if $opt_v;\n-\t\t\t$state = 11;\n-\t\t\tnext;\n-\t\t}\n-\t\tif (exists $ignorebranch{$branch}) {\n-\t\t\tprint STDERR \"Skipping $branch\\n\";\n-\t\t\t$state = 11;\n-\t\t\tnext;\n-\t\t}\n-\t\tif ($ancestor) {\n-\t\t\tif ($ancestor eq $branch) {\n-\t\t\t\tprint STDERR \"Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\\n\";\n-\t\t\t\t$ancestor = $opt_o;\n-\t\t\t}\n-\t\t\tif (defined get_headref(\"$remote/$branch\")) {\n-\t\t\t\tprint STDERR \"Branch $branch already exists!\\n\";\n-\t\t\t\t$state=11;\n-\t\t\t\tnext;\n-\t\t\t}\n-\t\t\tmy $id = get_headref(\"$remote/$ancestor\");\n-\t\t\tif (!$id) {\n-\t\t\t\tprint STDERR \"Branch $ancestor does not exist!\\n\";\n-\t\t\t\t$ignorebranch{$branch} = 1;\n-\t\t\t\t$state=11;\n-\t\t\t\tnext;\n-\t\t\t}\n-\n-\t\t\tsystem(qw(git update-ref -m cvsimport),\n-\t\t\t\t\"$remote/$branch\", $id);\n-\t\t\tif($? != 0) {\n-\t\t\t\tprint STDERR \"Could not create branch $branch\\n\";\n-\t\t\t\t$ignorebranch{$branch} = 1;\n-\t\t\t\t$state=11;\n-\t\t\t\tnext;\n-\t\t\t}\n-\t\t}\n-\t\t$last_branch = $branch if $branch ne $last_branch;\n-\t\t$state = 9;\n-\t} elsif ($state == 8) {\n-\t\t$logmsg .= \"$_\\n\";\n-\t} elsif ($state == 9 and /^\\s+(.+?):(INITIAL|\\d+(?:\\.\\d+)+)->(\\d+(?:\\.\\d+)+)\\s*$/) {\n-#\tVERSION:1.96->1.96.2.1\n-\t\tmy $init = ($2 eq \"INITIAL\");\n-\t\tmy $fn = $1;\n-\t\tmy $rev = $3;\n-\t\t$fn =~ s#^/+##;\n-\t\tif ($opt_S && $fn =~ m/$opt_S/) {\n-\t\t    print \"SKIPPING $fn v $rev\\n\";\n-\t\t    push(@skipped, $fn);\n-\t\t    next;\n-\t\t}\n-\t\tpush @commit_revisions, [$fn, $rev];\n-\t\tprint \"Fetching $fn   v $rev\\n\" if $opt_v;\n-\t\tmy ($tmpname, $size) = $cvs->file($fn,$rev);\n-\t\tif ($size == -1) {\n-\t\t\tpush(@old,$fn);\n-\t\t\tprint \"Drop $fn\\n\" if $opt_v;\n-\t\t} else {\n-\t\t\tprint \"\".($init ? \"New\" : \"Update\").\" $fn: $size bytes\\n\" if $opt_v;\n-\t\t\tmy $pid = open(my $F, '-|');\n-\t\t\tdie $! unless defined $pid;\n-\t\t\tif (!$pid) {\n-\t\t\t    exec(\"git\", \"hash-object\", \"-w\", $tmpname)\n-\t\t\t\tor die \"Cannot create object: $!\\n\";\n-\t\t\t}\n-\t\t\tmy $sha = <$F>;\n-\t\t\tchomp $sha;\n-\t\t\tclose $F;\n-\t\t\tmy $mode = pmode($cvs->{'mode'});\n-\t\t\tpush(@new,[$mode, $sha, $fn]); # may be resurrected!\n-\t\t}\n-\t\tunlink($tmpname);\n-\t} elsif ($state == 9 and /^\\s+(.+?):\\d+(?:\\.\\d+)+->(\\d+(?:\\.\\d+)+)\\(DEAD\\)\\s*$/) {\n-\t\tmy $fn = $1;\n-\t\tmy $rev = $2;\n-\t\t$fn =~ s#^/+##;\n-\t\tpush @commit_revisions, [$fn, $rev];\n-\t\tpush(@old,$fn);\n-\t\tprint \"Delete $fn\\n\" if $opt_v;\n-\t} elsif ($state == 9 and /^\\s*$/) {\n-\t\t$state = 10;\n-\t} elsif (($state == 9 or $state == 10) and /^-+$/) {\n-\t\t$commitcount++;\n-\t\tif ($opt_L && $commitcount > $opt_L) {\n-\t\t\tlast;\n-\t\t}\n-\t\tcommit();\n-\t\tif (($commitcount & 1023) == 0) {\n-\t\t\tsystem(qw(git repack -a -d));\n-\t\t}\n-\t\t$state = 1;\n-\t} elsif ($state == 11 and /^-+$/) {\n-\t\t$state = 1;\n-\t} elsif (/^-+$/) { # end of unknown-line processing\n-\t\t$state = 1;\n-\t} elsif ($state != 11) { # ignore stuff when skipping\n-\t\tprint STDERR \"* UNKNOWN LINE * $_\\n\";\n-\t}\n-}\n-commit() if $branch and $state != 11;\n-\n-unless ($opt_P) {\n-\tunlink($cvspsfile);\n-}\n-\n-# The heuristic of repacking every 1024 commits can leave a\n-# lot of unpacked data.  If there is more than 1MB worth of\n-# not-packed objects, repack once more.\n-my $line = `git count-objects`;\n-if ($line =~ /^(\\d+) objects, (\\d+) kilobytes$/) {\n-  my ($n_objects, $kb) = ($1, $2);\n-  1024 < $kb\n-    and system(qw(git repack -a -d));\n-}\n-\n-foreach my $git_index (values %index) {\n-    if ($git_index ne \"$git_dir/index\") {\n-\tunlink($git_index);\n-    }\n-}\n-\n-if (defined $orig_git_index) {\n-\t$ENV{GIT_INDEX_FILE} = $orig_git_index;\n-} else {\n-\tdelete $ENV{GIT_INDEX_FILE};\n-}\n-\n-# Now switch back to the branch we were in before all of this happened\n-if ($orig_branch) {\n-\tprint \"DONE.\\n\" if $opt_v;\n-\tif ($opt_i) {\n-\t\texit 0;\n-\t}\n-\tmy $tip_at_end = `git rev-parse --verify HEAD`;\n-\tif ($tip_at_start ne $tip_at_end) {\n-\t\tfor ($tip_at_start, $tip_at_end) { chomp; }\n-\t\tprint \"Fetched into the current branch.\\n\" if $opt_v;\n-\t\tsystem(qw(git read-tree -u -m),\n-\t\t       $tip_at_start, $tip_at_end);\n-\t\tdie \"Fast-forward update failed: $?\\n\" if $?;\n-\t}\n-\telse {\n-\t\tsystem(qw(git merge cvsimport HEAD), \"$remote/$opt_o\");\n-\t\tdie \"Could not merge $opt_o into the current branch.\\n\" if $?;\n-\t}\n-} else {\n-\t$orig_branch = \"master\";\n-\tprint \"DONE; creating $orig_branch branch\\n\" if $opt_v;\n-\tsystem(\"git\", \"update-ref\", \"refs/heads/master\", \"$remote/$opt_o\")\n-\t\tunless defined get_headref('refs/heads/master');\n-\tsystem(\"git\", \"symbolic-ref\", \"$remote/HEAD\", \"$remote/$opt_o\")\n-\t\tif ($opt_r && $opt_o ne 'HEAD');\n-\tsystem('git', 'update-ref', 'HEAD', \"$orig_branch\");\n-\tunless ($opt_i) {\n-\t\tsystem(qw(git checkout -f));\n-\t\tdie \"checkout failed: $?\\n\" if $?;\n-\t}\n-}\ndiff --git a/git-cvsimport.py b/git-cvsimport.py\nnew file mode 100755\nindex 0000000..6407e8a\n--- /dev/null\n+++ b/git-cvsimport.py\n@@ -0,0 +1,342 @@\n+#!/usr/bin/env python\n+#\n+# Import CVS history into git\n+#\n+# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.\n+#\n+# By Eric S. Raymond <esr@thyrsus.com>, December 2012\n+# May be redistributed under the license of the git project.\n+\n+import sys\n+\n+if sys.hexversion < 0x02060000:\n+    sys.stderr.write(\"git cvsimport: requires Python 2.6 or later.\\n\")\n+    sys.exit(1)\n+\n+import os, getopt, subprocess, tempfile, shutil\n+\n+DEBUG_COMMANDS = 1\n+\n+class Fatal(Exception):\n+    \"Unrecoverable error.\"\n+    def __init__(self, msg):\n+        Exception.__init__(self)\n+        self.msg = msg\n+\n+def do_or_die(dcmd, legend=\"\"):\n+    \"Either execute a command or raise a fatal exception.\"\n+    if legend:\n+        legend = \" \"  + legend\n+    if verbose >= DEBUG_COMMANDS:\n+        sys.stdout.write(\"git cvsimport: executing '%s'%s\\n\" % (dcmd, legend))\n+    try:\n+        retcode = subprocess.call(dcmd, shell=True)\n+        if retcode < 0:\n+            raise Fatal(\"git cvsimport: child was terminated by signal %d.\" % -retcode)\n+        elif retcode != 0:\n+            raise Fatal(\"git cvsimport: child returned %d.\" % retcode)\n+    except (OSError, IOError) as e:\n+        raise Fatal(\"git cvsimport: execution of %s%s failed: %s\" % (dcmd, legend, e))\n+\n+def capture_or_die(dcmd, legend=\"\"):\n+    \"Either execute a command and capture its output or die.\"\n+    if legend:\n+        legend = \" \"  + legend\n+    if verbose >= DEBUG_COMMANDS:\n+        sys.stdout.write(\"git cvsimport: executing '%s'%s\\n\" % (dcmd, legend))\n+    try:\n+        return subprocess.check_output(dcmd, shell=True)\n+    except subprocess.CalledProcessError as e:\n+        if e.returncode < 0:\n+            sys.stderr.write(\"git cvsimport: child was terminated by signal %d.\" % -e.returncode)\n+        elif e.returncode != 0:\n+            sys.stderr.write(\"git cvsimport: child returned %d.\" % e.returncode)\n+        sys.exit(1)\n+    \n+class cvsps:\n+    \"Method class for cvsps back end.\"\n+    def __init__(self):\n+        self.opts = \"\"\n+        self.revmap = None\n+    def set_repo(self, val):\n+        \"Set the repository root option.\"\n+        if not val.startswith(\":\"):\n+            if not val.startswith(os.sep):\n+                val = os.path.abspath(val)\n+            val = \":local:\" + val\n+        self.opts += \" --root '%s'\" % val\n+    def set_authormap(self, val):\n+        \"Set the author-map file.\"\n+        self.opts += \" -A '%s'\" % val\n+    def set_fuzz(self, val):\n+        \"Set the commit-similarity window.\"\n+        self.opts += \" -z %s\" % val\n+    def set_nokeywords(self):\n+        \"Suppress CVS keyword expansion.\"\n+        self.opts += \" -k\"\n+    def add_opts(self, val):\n+        \"Add options to the engine command line.\"\n+        self.opts += \" \" + val\n+    def set_exclusion(self, val):\n+        \"Set a file exclusion regexp.\"\n+        self.opts += \" -n -f '%s'\" % val\n+    def set_after(self, val):\n+        \"Set a date threshold for incremental import.\"\n+        self.opts += \" -d '%s'\" % val\n+    def set_revmap(self, val):\n+        \"Set the file to which the engine should dump a reference map.\"\n+        self.revmap = val\n+        self.opts += \" -R '%s'\" % self.revmap\n+    def set_module(self, val):\n+        \"Set the module to query.\"\n+        self.opts += \" \" + val\n+    def command(self):\n+        \"Emit the command implied by all previous options.\"\n+        return \"cvsps --fast-export \" + self.opts\n+\n+class cvs2git:\n+    \"Method class for cvs2git back end.\"\n+    def __init__(self):\n+        self.opts = \"\"\n+        self.modulepath = \".\"\n+    def set_authormap(self, _val):\n+        \"Set the author-map file.\"\n+        sys.stderr.write(\"git cvsimport: author maping is not supported with cvs2git.\\n\")\n+        sys.exit(1)\n+    def set_repo(self, _val):\n+        \"Set the repository root option.\"\n+        sys.stderr.write(\"git cvsimport: cvs2git must run within a repository checkout directory.\\n\")\n+        sys.exit(1)\n+    def set_fuzz(self, _val):\n+        \"Set the commit-similarity window.\"\n+        sys.stderr.write(\"git cvsimport: fuzz setting is not supported with cvs2git.\\n\")\n+        sys.exit(1)\n+    def set_nokeywords(self):\n+        \"Suppress CVS keyword expansion.\"\n+        self.opts += \" --keywords-off\"\n+    def add_opts(self, val):\n+        \"Add options to the engine command line.\"\n+        self.opts += \" \" + val\n+    def set_exclusion(self, val):\n+        \"Set a file exclusion regexp.\"\n+        self.opts += \" --exclude='%s'\" % val\n+    def set_after(self, _val):\n+        \"Set a date threshold for incremental import.\"\n+        sys.stderr.write(\"git cvsimport: incremental import is not supported with cvs2git.\\n\")\n+        sys.exit(1)\n+    def set_revmap(self, _val):\n+        \"Set the file to which the engine should dump a reference map.\"\n+        sys.stderr.write(\"git cvsimport: can't get a reference map from cvs2git.\\n\")\n+        sys.exit(1)\n+    def set_module(self, val):\n+        \"Set the module to query.\"\n+        self.modulepath = \" \" + val\n+    def command(self):\n+        \"Emit the command implied by all previous options.\"\n+        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)\n+\n+class filesource:\n+    \"Method class for file-source back end.\"\n+    def __init__(self, filename):\n+        self.filename = filename\n+    def __complain(self, legend):\n+        sys.stderr.write(\"git cvsimport: %s with file source.\\n\" % legend)\n+        sys.exit(1)\n+    def set_repo(self, _val):\n+        \"Set the repository root option.\"\n+        self.__complain(\"repository can't be set\")\n+    def set_authormap(self, _val):\n+        \"Set the author-map file.\"\n+        sys.stderr.write(\"git cvsimport: author maping is not supported with filesource.\\n\")\n+        sys.exit(1)\n+    def set_fuzz(self, _val):\n+        \"Set the commit-similarity window.\"\n+        self.__complain(\"fuzz can't be set\")\n+    def set_nokeywords(self, _val):\n+        \"Suppress CVS keyword expansion.\"\n+        self.__complain(\"keyword suppression can't be set\")\n+    def add_opts(self, _val):\n+        \"Add options to the engine command line.\"\n+        self.__complain(\"other options can't be set\")\n+    def set_exclusion(self, _val):\n+        \"Set a file exclusion regexp.\"\n+        self.__complain(\"exclusions can't be set\")\n+    def set_after(self, _val):\n+        \"Set a date threshold for incremental import.\"\n+        pass\n+    def set_revmap(self, _val):\n+        \"Set the file to which the engine should dump a reference map.\"\n+        sys.stderr.write(\"git cvsimport: can't get a reference map from cvs2git.\\n\")\n+        sys.exit(1)\n+    def set_module(self, _val):\n+        \"Set the module to query.\"\n+        self.__complain(\"module can't be set\")\n+    def command(self):\n+        \"Emit the command implied by all previous options.\"\n+        return \"cat \" + self.filename\n+\n+if __name__ == '__main__':\n+    if sys.hexversion < 0x02060000:\n+        sys.stderr.write(\"git cvsimport: requires Python 2.6 or later.\\n\")\n+        sys.exit(1)\n+    (options, arguments) = getopt.getopt(sys.argv[1:], \"vbe:d:C:r:o:ikus:p:z:P:S:aL:A:Rh\")\n+    verbose = 0\n+    bare = False\n+    root = None\n+    outdir = os.getcwd()\n+    remotize = False\n+    import_only = False\n+    underscore_to_dot = False\n+    slashsubst = None\n+    authormap = None\n+    revisionmap = False\n+    backend = cvsps()\n+    for (opt, val) in options:\n+        if opt == '-v':\n+            verbose += 1\n+        elif opt == '-b':\n+            bare = True\n+        elif opt == '-e':\n+            for cls in (cvsps, cvs2git):\n+                if cls.__name__ == val:\n+                    backend = cls()\n+                    break\n+            else:\n+                sys.stderr.write(\"git cvsimport: unknown engine %s.\\n\" % val)\n+                sys.exit(1)\n+        elif opt == '-d':\n+            backend.set_repo(val)\n+        elif opt == '-C':\n+            outdir = val\n+        elif opt == '-r':\n+            remotize = True\n+        elif opt == '-o':\n+            sys.stderr.write(\"git cvsimport: -o is no longer supported.\\n\")\n+            sys.exit(1)\n+        elif opt == '-i':\n+            import_only = True\n+        elif opt == '-k':\n+            backend.set_nokeywords()\n+        elif opt == '-u':\n+            underscore_to_dot = True\n+        elif opt == '-s':\n+            slashsubst = val\n+        elif opt == '-p':\n+            backend.add_opts(val.replace(\",\", \" \"))\n+        elif opt == '-z':\n+            backend.set_fuzz(val)\n+        elif opt == '-P':\n+            backend = filesource(val)\n+            sys.exit(1)\n+        elif opt in ('-m', '-M'):\n+            sys.stderr.write(\"git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\\n\")\n+            sys.exit(1)\n+        elif opt == '-S':\n+            backend.set_exclusion(val)\n+        elif opt == '-a':\n+            sys.stderr.write(\"git cvsimport: -a is no longer supported.\\n\")\n+            sys.exit(1)\n+        elif opt == '-L':\n+            sys.stderr.write(\"git cvsimport: -L is no longer supported.\\n\")\n+            sys.exit(1)\n+        elif opt == '-A':\n+            authormap = os.path.abspath(val)\n+        elif opt == '-R':\n+            revisionmap = True\n+        else:\n+            print \"\"\"\\\n+git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>]\n+     [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>]\n+     [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>]\n+     [<CVS_module>]\n+\"\"\"         \n+\n+    def metadata(fn):\n+        if bare:\n+            return fn\n+        else:\n+            return os.path.join(\".git\", fn) \n+    try:\n+        if outdir:\n+            try:\n+                # If the output directory does not exist, create it\n+                # and initialize it as a git repository.\n+                os.mkdir(outdir)\n+                do_or_die(\"git init --quiet \" + outdir)\n+            except OSError:\n+                # Otherwise, assume user wants incremental import.\n+                if not bare and not os.path.exists(os.path.join(outdir, \".git\")):\n+                    raise Fatal(\"output directory is not a git repository\")\n+                threshold = capture_or_die(\"git log -1 --format=%ct\").strip()\n+                backend.set_after(threshold)\n+        if revisionmap:\n+            backend.set_revmap(tempfile.mkstemp()[1])\n+            markmap = tempfile.mkstemp()[1]\n+        if arguments:\n+            backend.set_module(arguments[0])\n+        gitopts = \"\"\n+        if bare:\n+            gitopts += \" --bare\"\n+        if revisionmap:\n+            gitopts += \" --export-marks='%s'\" % markmap\n+        if authormap:\n+            shutil.copyfile(authormap, metadata(\"cvs_authors\"))\n+        if os.path.exists(metadata(\"cvs-authors\")):\n+            backend.set_authormap(metadata(\"cvs-authors\"))\n+        do_or_die(\"%s | (cd %s >/dev/null; git fast-import --quiet %s)\" \\\n+                  % (backend.command(), outdir, gitopts))\n+        os.chdir(outdir)\n+        if underscore_to_dot or slashsubst:\n+            tagnames = capture_or_die(\"git tag -l\")\n+            for tag in tagnames.split():\n+                if tag:\n+                    changed = tag\n+                    if underscore_to_dot:\n+                        changed = changed.replace(\"_\", \".\")\n+                    if slashsubst:\n+                        changed = changed.replace(os.sep, slashsubst)\n+                    if changed != tag:\n+                        do_or_die(\"git tag -f %s %s >/dev/null\" % (tag, changed))\n+        if underscore_to_dot or slashsubst or remotize:\n+            branchnames = capture_or_die(\"git branch -l\")\n+            for branch in branchnames.split():\n+                if branch:\n+                    # Ugh - fragile dependency on branch -l output format\n+                    branch = branch[2:]\n+                    changed = branch\n+                    if underscore_to_dot:\n+                        changed = changed.replace(\"_\", \".\")\n+                    if slashsubst:\n+                        changed = changed.replace(os.sep, slashsubst)\n+                    if remotize:\n+                        changed = os.path.join(\"remotes\", remotize, branch)\n+                    if changed != branch:\n+                        do_or_die(\"branch --m %s %s >/dev/null\" % (branch, changed))\n+        if revisionmap:\n+            refd = {}\n+            for line in open(backend.revmap):\n+                if line.startswith(\"#\"):\n+                    continue\n+                (fn, rev, mark) = line.split()\n+                refd[(fn, rev)] = mark\n+            markd = {}\n+            for line in open(markmap):\n+                if line.startswith(\"#\"):\n+                    continue\n+                (mark, hashd) = line.split()\n+                markd[mark] = hashd\n+            with open(metadata(\"cvs-revisions\"), \"a\") as wfp:\n+                for ((fn, rev), val) in refd.items():\n+                    if val in markd:\n+                        wfp.write(\"%s %s %s\\n\" % (fn, rev, markd[val]))\n+            os.remove(markmap)\n+            os.remove(backend.revmap)\n+        if not import_only and not bare:\n+            do_or_die(\"git checkout -q\")\n+    except Fatal, err:\n+        sys.stderr.write(\"git_cvsimport: \" + err.msg + \"\\n\")\n+        sys.exit(1)\n+    except KeyboardInterrupt:\n+        pass\n+\n+# end\ndiff --git a/t/t9601-cvsimport-vendor-branch.sh b/t/t9601-cvsimport-vendor-branch.sh\ndeleted file mode 100755\nindex 827d39f..0000000\n--- a/t/t9601-cvsimport-vendor-branch.sh\n+++ /dev/null\n@@ -1,85 +0,0 @@\n-#!/bin/sh\n-\n-# Description of the files in the repository:\n-#\n-#    imported-once.txt:\n-#\n-#       Imported once.  1.1 and 1.1.1.1 should be identical.\n-#\n-#    imported-twice.txt:\n-#\n-#       Imported twice.  HEAD should reflect the contents of the\n-#       second import (i.e., have the same contents as 1.1.1.2).\n-#\n-#    imported-modified.txt:\n-#\n-#       Imported, then modified on HEAD.  HEAD should reflect the\n-#       modification.\n-#\n-#    imported-modified-imported.txt:\n-#\n-#       Imported, then modified on HEAD, then imported again.\n-#\n-#    added-imported.txt,v:\n-#\n-#       Added with 'cvs add' to create 1.1, then imported with\n-#       completely different contents to create 1.1.1.1, therefore the\n-#       vendor branch was never the default branch.\n-#\n-#    imported-anonymously.txt:\n-#\n-#       Like imported-twice.txt, but with a vendor branch whose branch\n-#       tag has been removed.\n-\n-test_description='git cvsimport handling of vendor branches'\n-. ./lib-cvs.sh\n-\n-setup_cvs_test_repository t9601\n-\n-test_expect_success PERL 'import a module with a vendor branch' '\n-\n-\tgit cvsimport -C module-git module\n-\n-'\n-\n-test_expect_success PERL 'check HEAD out of cvs repository' 'test_cvs_co master'\n-\n-test_expect_success PERL 'check master out of git repository' 'test_git_co master'\n-\n-test_expect_success PERL 'check a file that was imported once' '\n-\n-\ttest_cmp_branch_file master imported-once.txt\n-\n-'\n-\n-test_expect_failure PERL 'check a file that was imported twice' '\n-\n-\ttest_cmp_branch_file master imported-twice.txt\n-\n-'\n-\n-test_expect_success PERL 'check a file that was imported then modified on HEAD' '\n-\n-\ttest_cmp_branch_file master imported-modified.txt\n-\n-'\n-\n-test_expect_success PERL 'check a file that was imported, modified, then imported again' '\n-\n-\ttest_cmp_branch_file master imported-modified-imported.txt\n-\n-'\n-\n-test_expect_success PERL 'check a file that was added to HEAD then imported' '\n-\n-\ttest_cmp_branch_file master added-imported.txt\n-\n-'\n-\n-test_expect_success PERL 'a vendor branch whose tag has been removed' '\n-\n-\ttest_cmp_branch_file master imported-anonymously.txt\n-\n-'\n-\n-test_done\ndiff --git a/t/t9601/cvsroot/.gitattributes b/t/t9601/cvsroot/.gitattributes\ndeleted file mode 100644\nindex 562b12e..0000000\n--- a/t/t9601/cvsroot/.gitattributes\n+++ /dev/null\n@@ -1 +0,0 @@\n-* -whitespace\ndiff --git a/t/t9601/cvsroot/CVSROOT/.gitignore b/t/t9601/cvsroot/CVSROOT/.gitignore\ndeleted file mode 100644\nindex 3bb9b34..0000000\n--- a/t/t9601/cvsroot/CVSROOT/.gitignore\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-history\n-val-tags\ndiff --git a/t/t9601/cvsroot/module/added-imported.txt,v b/t/t9601/cvsroot/module/added-imported.txt,v\ndeleted file mode 100644\nindex 5f83072..0000000\n--- a/t/t9601/cvsroot/module/added-imported.txt,v\n+++ /dev/null\n@@ -1,44 +0,0 @@\n-head\t1.1;\n-access;\n-symbols\n-\tvtag-4:1.1.1.1\n-\tvbranchA:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.1\n-date\t2004.02.09.15.43.15;\tauthor kfogel;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2004.02.09.15.43.16;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.1\n-log\n-@Add a file to the working copy.\n-@\n-text\n-@Adding this file, before importing it with different contents.\n-@\n-\n-\n-1.1.1.1\n-log\n-@Import (vbranchA, vtag-4).\n-@\n-text\n-@d1 1\n-a1 1\n-This is vtag-4 (on vbranchA) of added-then-imported.txt.\n-@\n-\ndiff --git a/t/t9601/cvsroot/module/imported-anonymously.txt,v b/t/t9601/cvsroot/module/imported-anonymously.txt,v\ndeleted file mode 100644\nindex 55e1b0c..0000000\n--- a/t/t9601/cvsroot/module/imported-anonymously.txt,v\n+++ /dev/null\n@@ -1,42 +0,0 @@\n-head\t1.1;\n-branch\t1.1.1;\n-access;\n-symbols\n-\tvtag-1:1.1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@This is vtag-1 (on vbranchA) of imported-anonymously.txt.\n-@\n-\n-\n-1.1.1.1\n-log\n-@Import (vbranchA, vtag-1).\n-@\n-text\n-@@\n-\n-\ndiff --git a/t/t9601/cvsroot/module/imported-modified-imported.txt,v b/t/t9601/cvsroot/module/imported-modified-imported.txt,v\ndeleted file mode 100644\nindex e5830ae..0000000\n--- a/t/t9601/cvsroot/module/imported-modified-imported.txt,v\n+++ /dev/null\n@@ -1,76 +0,0 @@\n-head\t1.2;\n-access;\n-symbols\n-\tvtag-2:1.1.1.2\n-\tvtag-1:1.1.1.1\n-\tvbranchA:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.2\n-date\t2004.02.09.15.43.14;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t1.1;\n-\n-1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t1.1.1.2;\n-\n-1.1.1.2\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.2\n-log\n-@First regular commit, to imported-modified-imported.txt, on HEAD.\n-@\n-text\n-@This is a modification of imported-modified-imported.txt on HEAD.\n-It should supersede the version from the vendor branch.\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d1 2\n-a2 1\n-This is vtag-1 (on vbranchA) of imported-modified-imported.txt.\n-@\n-\n-\n-1.1.1.1\n-log\n-@Import (vbranchA, vtag-1).\n-@\n-text\n-@@\n-\n-\n-1.1.1.2\n-log\n-@Import (vbranchA, vtag-2).\n-@\n-text\n-@d1 1\n-a1 1\n-This is vtag-2 (on vbranchA) of imported-modified-imported.txt.\n-@\n-\n-\ndiff --git a/t/t9601/cvsroot/module/imported-modified.txt,v b/t/t9601/cvsroot/module/imported-modified.txt,v\ndeleted file mode 100644\nindex bbcfe44..0000000\n--- a/t/t9601/cvsroot/module/imported-modified.txt,v\n+++ /dev/null\n@@ -1,59 +0,0 @@\n-head\t1.2;\n-access;\n-symbols\n-\tvtag-1:1.1.1.1\n-\tvbranchA:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.2\n-date\t2004.02.09.15.43.14;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t1.1;\n-\n-1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.2\n-log\n-@Commit on HEAD.\n-@\n-text\n-@This is a modification of imported-modified.txt on HEAD.\n-It should supersede the version from the vendor branch.\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d1 2\n-a2 1\n-This is vtag-1 (on vbranchA) of imported-modified.txt.\n-@\n-\n-\n-1.1.1.1\n-log\n-@Import (vbranchA, vtag-1).\n-@\n-text\n-@@\n-\n-\ndiff --git a/t/t9601/cvsroot/module/imported-once.txt,v b/t/t9601/cvsroot/module/imported-once.txt,v\ndeleted file mode 100644\nindex c5dd82b..0000000\n--- a/t/t9601/cvsroot/module/imported-once.txt,v\n+++ /dev/null\n@@ -1,43 +0,0 @@\n-head\t1.1;\n-branch\t1.1.1;\n-access;\n-symbols\n-\tvtag-1:1.1.1.1\n-\tvbranchA:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@This is vtag-1 (on vbranchA) of imported-once.txt.\n-@\n-\n-\n-1.1.1.1\n-log\n-@Import (vbranchA, vtag-1).\n-@\n-text\n-@@\n-\n-\ndiff --git a/t/t9601/cvsroot/module/imported-twice.txt,v b/t/t9601/cvsroot/module/imported-twice.txt,v\ndeleted file mode 100644\nindex d1f3f1b..0000000\n--- a/t/t9601/cvsroot/module/imported-twice.txt,v\n+++ /dev/null\n@@ -1,60 +0,0 @@\n-head\t1.1;\n-branch\t1.1.1;\n-access;\n-symbols\n-\tvtag-2:1.1.1.2\n-\tvtag-1:1.1.1.1\n-\tvbranchA:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t1.1.1.2;\n-\n-1.1.1.2\n-date\t2004.02.09.15.43.13;\tauthor kfogel;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@This is vtag-1 (on vbranchA) of imported-twice.txt.\n-@\n-\n-\n-1.1.1.1\n-log\n-@Import (vbranchA, vtag-1).\n-@\n-text\n-@@\n-\n-\n-1.1.1.2\n-log\n-@Import (vbranchA, vtag-2).\n-@\n-text\n-@d1 1\n-a1 1\n-This is vtag-2 (on vbranchA) of imported-twice.txt.\n-@\n-\n-\ndiff --git a/t/t9602-cvsimport-branches-tags.sh b/t/t9602-cvsimport-branches-tags.sh\ndeleted file mode 100755\nindex e1db323..0000000\n--- a/t/t9602-cvsimport-branches-tags.sh\n+++ /dev/null\n@@ -1,78 +0,0 @@\n-#!/bin/sh\n-\n-# A description of the repository used for this test can be found in\n-# t9602/README.\n-\n-test_description='git cvsimport handling of branches and tags'\n-. ./lib-cvs.sh\n-\n-setup_cvs_test_repository t9602\n-\n-test_expect_success PERL 'import module' '\n-\n-\tgit cvsimport -C module-git module\n-\n-'\n-\n-test_expect_success PERL 'test branch master' '\n-\n-\ttest_cmp_branch_tree master\n-\n-'\n-\n-test_expect_success PERL 'test branch vendorbranch' '\n-\n-\ttest_cmp_branch_tree vendorbranch\n-\n-'\n-\n-test_expect_failure PERL 'test branch B_FROM_INITIALS' '\n-\n-\ttest_cmp_branch_tree B_FROM_INITIALS\n-\n-'\n-\n-test_expect_failure PERL 'test branch B_FROM_INITIALS_BUT_ONE' '\n-\n-\ttest_cmp_branch_tree B_FROM_INITIALS_BUT_ONE\n-\n-'\n-\n-test_expect_failure PERL 'test branch B_MIXED' '\n-\n-\ttest_cmp_branch_tree B_MIXED\n-\n-'\n-\n-test_expect_success PERL 'test branch B_SPLIT' '\n-\n-\ttest_cmp_branch_tree B_SPLIT\n-\n-'\n-\n-test_expect_failure PERL 'test tag vendortag' '\n-\n-\ttest_cmp_branch_tree vendortag\n-\n-'\n-\n-test_expect_success PERL 'test tag T_ALL_INITIAL_FILES' '\n-\n-\ttest_cmp_branch_tree T_ALL_INITIAL_FILES\n-\n-'\n-\n-test_expect_failure PERL 'test tag T_ALL_INITIAL_FILES_BUT_ONE' '\n-\n-\ttest_cmp_branch_tree T_ALL_INITIAL_FILES_BUT_ONE\n-\n-'\n-\n-test_expect_failure PERL 'test tag T_MIXED' '\n-\n-\ttest_cmp_branch_tree T_MIXED\n-\n-'\n-\n-\n-test_done\ndiff --git a/t/t9602/README b/t/t9602/README\ndeleted file mode 100644\nindex c231e0f..0000000\n--- a/t/t9602/README\n+++ /dev/null\n@@ -1,62 +0,0 @@\n-This repository is for testing the ability to group revisions\n-correctly along tags and branches.  Here is its history:\n-\n-  1.  The initial import (revision 1.1 of everybody) created a\n-      directory structure with a file named `default' in each dir:\n-\n-            ./\n-              default\n-              sub1/default\n-                   subsubA/default\n-                   subsubB/default\n-              sub2/default\n-                   subsubA/default\n-              sub3/default\n-\n-  2.  Then tagged everyone with T_ALL_INITIAL_FILES.\n-\n-  3.  Then tagged everyone except sub1/subsubB/default with\n-      T_ALL_INITIAL_FILES_BUT_ONE.\n-\n-  4.  Then created branch B_FROM_INITIALS on everyone.\n-\n-  5.  Then created branch B_FROM_INITIALS_BUT_ONE on everyone except\n-      /sub1/subsubB/default.\n-\n-  6.  Then committed modifications to two files: sub3/default, and\n-      sub1/subsubA/default.\n-\n-  7.  Then committed a modification to all 7 files.\n-\n-  8.  Then backdated sub3/default to revision 1.2, and\n-      sub2/subsubA/default to revision 1.1, and tagged with T_MIXED.\n-\n-  9.  Same as 8, but tagged with -b to create branch B_MIXED.\n-\n-  10. Switched the working copy to B_MIXED, and added\n-      sub2/branch_B_MIXED_only.  (That's why the RCS file is in\n-      sub2/Attic/ -- it never existed on trunk.)\n-\n-  11. In one commit, modified default, sub1/default, and\n-      sub2/subsubA/default, on branch B_MIXED.\n-\n-  12. Did \"cvs up -A\" on sub2/default, then in one commit, made a\n-      change to sub2/default and sub2/branch_B_MIXED_only.  So this\n-      commit should be spread between the branch and the trunk.\n-\n-  13. Do \"cvs up -A\" to get everyone back to trunk, then make a new\n-      branch B_SPLIT on everyone except sub1/subsubB/default,v.\n-\n-  14. Switch to branch B_SPLIT (see sub1/subsubB/default disappear)\n-      and commit a change that affects everyone except sub3/default.\n-\n-  15. An hour or so later, \"cvs up -A\" to get sub1/subsubB/default\n-      back, then commit a change on that file, on trunk.  (It's\n-      important that this change happened after the previous commits\n-      on B_SPLIT.)\n-\n-  16. Branch sub1/subsubB/default to B_SPLIT, then \"cvs up -r B_SPLIT\"\n-      to switch the whole working copy to the branch.\n-\n-  17. Commit a change on B_SPLIT, to sub1/subsubB/default and\n-      sub3/default.\ndiff --git a/t/t9602/cvsroot/.gitattributes b/t/t9602/cvsroot/.gitattributes\ndeleted file mode 100644\nindex 562b12e..0000000\n--- a/t/t9602/cvsroot/.gitattributes\n+++ /dev/null\n@@ -1 +0,0 @@\n-* -whitespace\ndiff --git a/t/t9602/cvsroot/CVSROOT/.gitignore b/t/t9602/cvsroot/CVSROOT/.gitignore\ndeleted file mode 100644\nindex 3bb9b34..0000000\n--- a/t/t9602/cvsroot/CVSROOT/.gitignore\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-history\n-val-tags\ndiff --git a/t/t9602/cvsroot/module/default,v b/t/t9602/cvsroot/module/default,v\ndeleted file mode 100644\nindex 3b68382..0000000\n--- a/t/t9602/cvsroot/module/default,v\n+++ /dev/null\n@@ -1,102 +0,0 @@\n-head\t1.2;\n-access;\n-symbols\n-\tB_SPLIT:1.2.0.4\n-\tB_MIXED:1.2.0.2\n-\tT_MIXED:1.2\n-\tB_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4\n-\tB_FROM_INITIALS:1.1.1.1.0.2\n-\tT_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1\n-\tT_ALL_INITIAL_FILES:1.1.1.1\n-\tvendortag:1.1.1.1\n-\tvendorbranch:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.2\n-date\t2003.05.23.00.17.53;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.2.2.1\n-\t1.2.4.1;\n-next\t1.1;\n-\n-1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.2.2.1\n-date\t2003.05.23.00.31.36;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.2.4.1\n-date\t2003.06.03.03.20.31;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.2\n-log\n-@Second commit to proj, affecting all 7 files.\n-@\n-text\n-@This is the file `default' in the top level of the project.\n-\n-Every directory in the `proj' project has a file named `default'.\n-\n-This line was added in the second commit (affecting all 7 files).\n-@\n-\n-\n-1.2.4.1\n-log\n-@First change on branch B_SPLIT.\n-\n-This change excludes sub3/default, because it was not part of this\n-commit, and sub1/subsubB/default, which is not even on the branch yet.\n-@\n-text\n-@a5 2\n-\n-First change on branch B_SPLIT.\n-@\n-\n-\n-1.2.2.1\n-log\n-@Modify three files, on branch B_MIXED.\n-@\n-text\n-@a5 2\n-\n-This line was added on branch B_MIXED only (affecting 3 files).\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d4 2\n-@\n-\n-\n-1.1.1.1\n-log\n-@Initial import.\n-@\n-text\n-@@\ndiff --git a/t/t9602/cvsroot/module/sub1/default,v b/t/t9602/cvsroot/module/sub1/default,v\ndeleted file mode 100644\nindex b7fdccd..0000000\n--- a/t/t9602/cvsroot/module/sub1/default,v\n+++ /dev/null\n@@ -1,102 +0,0 @@\n-head\t1.2;\n-access;\n-symbols\n-\tB_SPLIT:1.2.0.4\n-\tB_MIXED:1.2.0.2\n-\tT_MIXED:1.2\n-\tB_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4\n-\tB_FROM_INITIALS:1.1.1.1.0.2\n-\tT_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1\n-\tT_ALL_INITIAL_FILES:1.1.1.1\n-\tvendortag:1.1.1.1\n-\tvendorbranch:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.2\n-date\t2003.05.23.00.17.53;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.2.2.1\n-\t1.2.4.1;\n-next\t1.1;\n-\n-1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.2.2.1\n-date\t2003.05.23.00.31.36;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.2.4.1\n-date\t2003.06.03.03.20.31;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.2\n-log\n-@Second commit to proj, affecting all 7 files.\n-@\n-text\n-@This is sub1/default.\n-\n-Every directory in the `proj' project has a file named `default'.\n-\n-This line was added in the second commit (affecting all 7 files).\n-@\n-\n-\n-1.2.4.1\n-log\n-@First change on branch B_SPLIT.\n-\n-This change excludes sub3/default, because it was not part of this\n-commit, and sub1/subsubB/default, which is not even on the branch yet.\n-@\n-text\n-@a5 2\n-\n-First change on branch B_SPLIT.\n-@\n-\n-\n-1.2.2.1\n-log\n-@Modify three files, on branch B_MIXED.\n-@\n-text\n-@a5 2\n-\n-This line was added on branch B_MIXED only (affecting 3 files).\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d4 2\n-@\n-\n-\n-1.1.1.1\n-log\n-@Initial import.\n-@\n-text\n-@@\ndiff --git a/t/t9602/cvsroot/module/sub1/subsubA/default,v b/t/t9602/cvsroot/module/sub1/subsubA/default,v\ndeleted file mode 100644\nindex 472b7b2..0000000\n--- a/t/t9602/cvsroot/module/sub1/subsubA/default,v\n+++ /dev/null\n@@ -1,101 +0,0 @@\n-head\t1.3;\n-access;\n-symbols\n-\tB_SPLIT:1.3.0.4\n-\tB_MIXED:1.3.0.2\n-\tT_MIXED:1.3\n-\tB_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4\n-\tB_FROM_INITIALS:1.1.1.1.0.2\n-\tT_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1\n-\tT_ALL_INITIAL_FILES:1.1.1.1\n-\tvendortag:1.1.1.1\n-\tvendorbranch:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.3\n-date\t2003.05.23.00.17.53;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.3.4.1;\n-next\t1.2;\n-\n-1.2\n-date\t2003.05.23.00.15.26;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t1.1;\n-\n-1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.3.4.1\n-date\t2003.06.03.03.20.31;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.3\n-log\n-@Second commit to proj, affecting all 7 files.\n-@\n-text\n-@This is sub1/subsubA/default.\n-\n-Every directory in the `proj' project has a file named `default'.\n-\n-This line was added by the first commit (affecting two files).\n-\n-This line was added in the second commit (affecting all 7 files).\n-@\n-\n-\n-1.3.4.1\n-log\n-@First change on branch B_SPLIT.\n-\n-This change excludes sub3/default, because it was not part of this\n-commit, and sub1/subsubB/default, which is not even on the branch yet.\n-@\n-text\n-@a7 2\n-\n-First change on branch B_SPLIT.\n-@\n-\n-\n-1.2\n-log\n-@First commit to proj, affecting two files.\n-@\n-text\n-@d6 2\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d4 2\n-@\n-\n-\n-1.1.1.1\n-log\n-@Initial import.\n-@\n-text\n-@@\ndiff --git a/t/t9602/cvsroot/module/sub1/subsubB/default,v b/t/t9602/cvsroot/module/sub1/subsubB/default,v\ndeleted file mode 100644\nindex fe6efa4..0000000\n--- a/t/t9602/cvsroot/module/sub1/subsubB/default,v\n+++ /dev/null\n@@ -1,107 +0,0 @@\n-head\t1.3;\n-access;\n-symbols\n-\tB_SPLIT:1.3.0.2\n-\tB_MIXED:1.2.0.2\n-\tT_MIXED:1.2\n-\tB_FROM_INITIALS:1.1.1.1.0.2\n-\tT_ALL_INITIAL_FILES:1.1.1.1\n-\tvendortag:1.1.1.1\n-\tvendorbranch:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.3\n-date\t2003.06.03.04.29.14;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.3.2.1;\n-next\t1.2;\n-\n-1.2\n-date\t2003.05.23.00.17.53;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t1.1;\n-\n-1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.3.2.1\n-date\t2003.06.03.04.33.13;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.3\n-log\n-@A trunk change to sub1/subsubB/default.  This was committed about an\n-hour after an earlier change that affected most files on branch\n-B_SPLIT.  This file is not on that branch yet, but after this commit,\n-we'll branch to B_SPLIT, albeit rooted in a revision that didn't exist\n-at the time the rest of B_SPLIT was created.\n-@\n-text\n-@This is sub1/subsubB/default.\n-\n-Every directory in the `proj' project has a file named `default'.\n-\n-This line was added in the second commit (affecting all 7 files).\n-\n-This bit was committed on trunk about an hour after an earlier change\n-to everyone else on branch B_SPLIT.  Afterwards, we'll finally branch\n-this file to B_SPLIT, but rooted in a revision that didn't exist at\n-the time the rest of B_SPLIT was created.\n-@\n-\n-\n-1.3.2.1\n-log\n-@This change affects sub3/default and sub1/subsubB/default, on branch\n-B_SPLIT.  Note that the latter file did not even exist on this branch\n-until after some other files had had revisions committed on B_SPLIT.\n-@\n-text\n-@a10 4\n-\n-This change affects sub3/default and sub1/subsubB/default, on branch\n-B_SPLIT.  Note that the latter file did not even exist on this branch\n-until after some other files had had revisions committed on B_SPLIT.\n-@\n-\n-\n-1.2\n-log\n-@Second commit to proj, affecting all 7 files.\n-@\n-text\n-@d6 5\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d4 2\n-@\n-\n-\n-1.1.1.1\n-log\n-@Initial import.\n-@\n-text\n-@@\ndiff --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\ndeleted file mode 100644\nindex 34c9789..0000000\n--- a/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v\n+++ /dev/null\n@@ -1,59 +0,0 @@\n-head\t1.1;\n-access;\n-symbols\n-\tB_MIXED:1.1.0.2;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.1\n-date\t2003.05.23.00.25.26;\tauthor jrandom;\tstate dead;\n-branches\n-\t1.1.2.1;\n-next\t;\n-\n-1.1.2.1\n-date\t2003.05.23.00.25.26;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t1.1.2.2;\n-\n-1.1.2.2\n-date\t2003.05.23.00.48.51;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.1\n-log\n-@file branch_B_MIXED_only was initially added on branch B_MIXED.\n-@\n-text\n-@@\n-\n-\n-1.1.2.1\n-log\n-@Add a file on branch B_MIXED.\n-@\n-text\n-@a0 1\n-This file was added on branch B_MIXED.  It never existed on trunk.\n-@\n-\n-\n-1.1.2.2\n-log\n-@A single commit affecting one file on branch B_MIXED and one on trunk.\n-@\n-text\n-@a1 3\n-\n-The same commit added these two lines here on branch B_MIXED, and two\n-similar lines to ./default on trunk.\n-@\n-\n-\ndiff --git a/t/t9602/cvsroot/module/sub2/default,v b/t/t9602/cvsroot/module/sub2/default,v\ndeleted file mode 100644\nindex 018f7f8..0000000\n--- a/t/t9602/cvsroot/module/sub2/default,v\n+++ /dev/null\n@@ -1,102 +0,0 @@\n-head\t1.3;\n-access;\n-symbols\n-\tB_SPLIT:1.3.0.2\n-\tB_MIXED:1.2.0.2\n-\tT_MIXED:1.2\n-\tB_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4\n-\tB_FROM_INITIALS:1.1.1.1.0.2\n-\tT_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1\n-\tT_ALL_INITIAL_FILES:1.1.1.1\n-\tvendortag:1.1.1.1\n-\tvendorbranch:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.3\n-date\t2003.05.23.00.48.51;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.3.2.1;\n-next\t1.2;\n-\n-1.2\n-date\t2003.05.23.00.17.53;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t1.1;\n-\n-1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.3.2.1\n-date\t2003.06.03.03.20.31;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.3\n-log\n-@A single commit affecting one file on branch B_MIXED and one on trunk.\n-@\n-text\n-@This is sub2/default.\n-\n-Every directory in the `proj' project has a file named `default'.\n-\n-This line was added in the second commit (affecting all 7 files).\n-\n-The same commit added these two lines here on trunk, and two similar\n-lines to ./branch_B_MIXED_only on branch B_MIXED.\n-@\n-\n-\n-1.3.2.1\n-log\n-@First change on branch B_SPLIT.\n-\n-This change excludes sub3/default, because it was not part of this\n-commit, and sub1/subsubB/default, which is not even on the branch yet.\n-@\n-text\n-@a8 2\n-\n-First change on branch B_SPLIT.\n-@\n-\n-\n-1.2\n-log\n-@Second commit to proj, affecting all 7 files.\n-@\n-text\n-@d6 3\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d4 2\n-@\n-\n-\n-1.1.1.1\n-log\n-@Initial import.\n-@\n-text\n-@@\ndiff --git a/t/t9602/cvsroot/module/sub2/subsubA/default,v b/t/t9602/cvsroot/module/sub2/subsubA/default,v\ndeleted file mode 100644\nindex d13242c..0000000\n--- a/t/t9602/cvsroot/module/sub2/subsubA/default,v\n+++ /dev/null\n@@ -1,102 +0,0 @@\n-head\t1.2;\n-access;\n-symbols\n-\tB_SPLIT:1.2.0.2\n-\tB_MIXED:1.1.0.2\n-\tT_MIXED:1.1\n-\tB_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4\n-\tB_FROM_INITIALS:1.1.1.1.0.2\n-\tT_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1\n-\tT_ALL_INITIAL_FILES:1.1.1.1\n-\tvendortag:1.1.1.1\n-\tvendorbranch:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.2\n-date\t2003.05.23.00.17.53;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.2.2.1;\n-next\t1.1;\n-\n-1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.1.1.1\n-\t1.1.2.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.1.2.1\n-date\t2003.05.23.00.31.36;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.2.2.1\n-date\t2003.06.03.03.20.31;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.2\n-log\n-@Second commit to proj, affecting all 7 files.\n-@\n-text\n-@This is sub2/subsub2/default.\n-\n-Every directory in the `proj' project has a file named `default'.\n-\n-This line was added in the second commit (affecting all 7 files).\n-@\n-\n-\n-1.2.2.1\n-log\n-@First change on branch B_SPLIT.\n-\n-This change excludes sub3/default, because it was not part of this\n-commit, and sub1/subsubB/default, which is not even on the branch yet.\n-@\n-text\n-@a5 2\n-\n-First change on branch B_SPLIT.\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d4 2\n-@\n-\n-\n-1.1.2.1\n-log\n-@Modify three files, on branch B_MIXED.\n-@\n-text\n-@a3 2\n-\n-This line was added on branch B_MIXED only (affecting 3 files).\n-@\n-\n-\n-1.1.1.1\n-log\n-@Initial import.\n-@\n-text\n-@@\ndiff --git a/t/t9602/cvsroot/module/sub3/default,v b/t/t9602/cvsroot/module/sub3/default,v\ndeleted file mode 100644\nindex 88e4567..0000000\n--- a/t/t9602/cvsroot/module/sub3/default,v\n+++ /dev/null\n@@ -1,102 +0,0 @@\n-head\t1.3;\n-access;\n-symbols\n-\tB_SPLIT:1.3.0.2\n-\tB_MIXED:1.2.0.2\n-\tT_MIXED:1.2\n-\tB_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4\n-\tB_FROM_INITIALS:1.1.1.1.0.2\n-\tT_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1\n-\tT_ALL_INITIAL_FILES:1.1.1.1\n-\tvendortag:1.1.1.1\n-\tvendorbranch:1.1.1;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.3\n-date\t2003.05.23.00.17.53;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.3.2.1;\n-next\t1.2;\n-\n-1.2\n-date\t2003.05.23.00.15.26;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t1.1;\n-\n-1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches\n-\t1.1.1.1;\n-next\t;\n-\n-1.1.1.1\n-date\t2003.05.22.23.20.19;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.3.2.1\n-date\t2003.06.03.04.33.13;\tauthor jrandom;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.3\n-log\n-@Second commit to proj, affecting all 7 files.\n-@\n-text\n-@This is sub3/default.\n-\n-Every directory in the `proj' project has a file named `default'.\n-\n-This line was added by the first commit (affecting two files).\n-\n-This line was added in the second commit (affecting all 7 files).\n-@\n-\n-\n-1.3.2.1\n-log\n-@This change affects sub3/default and sub1/subsubB/default, on branch\n-B_SPLIT.  Note that the latter file did not even exist on this branch\n-until after some other files had had revisions committed on B_SPLIT.\n-@\n-text\n-@a7 4\n-\n-This change affects sub3/default and sub1/subsubB/default, on branch\n-B_SPLIT.  Note that the latter file did not even exist on this branch\n-until after some other files had had revisions committed on B_SPLIT.\n-@\n-\n-\n-1.2\n-log\n-@First commit to proj, affecting two files.\n-@\n-text\n-@d6 2\n-@\n-\n-\n-1.1\n-log\n-@Initial revision\n-@\n-text\n-@d4 2\n-@\n-\n-\n-1.1.1.1\n-log\n-@Initial import.\n-@\n-text\n-@@\ndiff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh\ndeleted file mode 100755\nindex 52034c8..0000000\n--- a/t/t9603-cvsimport-patchsets.sh\n+++ /dev/null\n@@ -1,39 +0,0 @@\n-#!/bin/sh\n-\n-# Structure of the test cvs repository\n-#\n-# Message   File:Content         Commit Time\n-# Rev 1     a: 1.1               2009-02-21 19:11:43 +0100\n-# Rev 2     a: 1.2    b: 1.1     2009-02-21 19:11:14 +0100\n-# Rev 3               b: 1.2     2009-02-21 19:11:43 +0100\n-#\n-# As you can see the commit of Rev 3 has the same time as\n-# Rev 1 this leads to a broken import because of a cvsps\n-# bug.\n-\n-test_description='git cvsimport testing for correct patchset estimation'\n-. ./lib-cvs.sh\n-\n-setup_cvs_test_repository t9603\n-\n-test_expect_failure 'import with criss cross times on revisions' '\n-\n-    git cvsimport -p\"-x\" -C module-git module &&\n-    (cd module-git &&\n-        git log --pretty=format:%s > ../actual-master &&\n-        git log A~2..A --pretty=\"format:%s %ad\" -- > ../actual-A &&\n-        echo \"\" >> ../actual-master &&\n-\techo \"\" >> ../actual-A\n-    ) &&\n-    echo \"Rev 4\n-Rev 3\n-Rev 2\n-Rev 1\" > expect-master &&\n-    test_cmp actual-master expect-master &&\n-\n-    echo \"Rev 5 Branch A Wed Mar 11 19:09:10 2009 +0000\n-Rev 4 Branch A Wed Mar 11 19:03:52 2009 +0000\" > expect-A &&\n-    test_cmp actual-A expect-A\n-'\n-\n-test_done\ndiff --git a/t/t9603/cvsroot/.gitattributes b/t/t9603/cvsroot/.gitattributes\ndeleted file mode 100644\nindex 562b12e..0000000\n--- a/t/t9603/cvsroot/.gitattributes\n+++ /dev/null\n@@ -1 +0,0 @@\n-* -whitespace\ndiff --git a/t/t9603/cvsroot/CVSROOT/.gitignore b/t/t9603/cvsroot/CVSROOT/.gitignore\ndeleted file mode 100644\nindex 3bb9b34..0000000\n--- a/t/t9603/cvsroot/CVSROOT/.gitignore\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-history\n-val-tags\ndiff --git a/t/t9603/cvsroot/module/a,v b/t/t9603/cvsroot/module/a,v\ndeleted file mode 100644\nindex ba8fd5a..0000000\n--- a/t/t9603/cvsroot/module/a,v\n+++ /dev/null\n@@ -1,74 +0,0 @@\n-head\t1.2;\n-access;\n-symbols\n-\tA:1.2.0.2;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.2\n-date\t2009.02.21.18.11.14;\tauthor tester;\tstate Exp;\n-branches\n-\t1.2.2.1;\n-next\t1.1;\n-\n-1.1\n-date\t2009.02.21.18.11.43;\tauthor tester;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.2.2.1\n-date\t2009.03.11.19.03.52;\tauthor tester;\tstate Exp;\n-branches;\n-next\t1.2.2.2;\n-\n-1.2.2.2\n-date\t2009.03.11.19.09.10;\tauthor tester;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.2\n-log\n-@Rev 2\n-@\n-text\n-@1.2\n-@\n-\n-\n-1.2.2.1\n-log\n-@Rev 4 Branch A\n-@\n-text\n-@d1 1\n-a1 1\n-1.2.2.1\n-@\n-\n-\n-1.2.2.2\n-log\n-@Rev 5 Branch A\n-@\n-text\n-@d1 1\n-a1 1\n-1.2.2.2\n-@\n-\n-\n-1.1\n-log\n-@Rev 1\n-@\n-text\n-@d1 1\n-a1 1\n-1.1\n-@\ndiff --git a/t/t9603/cvsroot/module/b,v b/t/t9603/cvsroot/module/b,v\ndeleted file mode 100644\nindex d268855..0000000\n--- a/t/t9603/cvsroot/module/b,v\n+++ /dev/null\n@@ -1,90 +0,0 @@\n-head\t1.3;\n-access;\n-symbols\n-\tA:1.2.0.2;\n-locks; strict;\n-comment\t@# @;\n-\n-\n-1.3\n-date\t2009.03.11.19.05.08;\tauthor tester;\tstate Exp;\n-branches;\n-next\t1.2;\n-\n-1.2\n-date\t2009.02.21.18.11.43;\tauthor tester;\tstate Exp;\n-branches\n-\t1.2.2.1;\n-next\t1.1;\n-\n-1.1\n-date\t2009.02.21.18.11.14;\tauthor tester;\tstate Exp;\n-branches;\n-next\t;\n-\n-1.2.2.1\n-date\t2009.03.11.19.03.52;\tauthor tester;\tstate Exp;\n-branches;\n-next\t1.2.2.2;\n-\n-1.2.2.2\n-date\t2009.03.11.19.09.10;\tauthor tester;\tstate Exp;\n-branches;\n-next\t;\n-\n-\n-desc\n-@@\n-\n-\n-1.3\n-log\n-@Rev 4\n-@\n-text\n-@1.3\n-@\n-\n-\n-1.2\n-log\n-@Rev 3\n-@\n-text\n-@d1 1\n-a1 1\n-1.2\n-@\n-\n-\n-1.2.2.1\n-log\n-@Rev 4 Branch A\n-@\n-text\n-@d1 1\n-a1 1\n-1.2.2.1\n-@\n-\n-\n-1.2.2.2\n-log\n-@Rev 5 Branch A\n-@\n-text\n-@d1 1\n-a1 1\n-1.2\n-@\n-\n-\n-1.1\n-log\n-@Rev 2\n-@\n-text\n-@d1 1\n-a1 1\n-1.1\n-@\n-- \n1.8.1.rc2\n\n\n\n-- \n\t\t<a href=\"http://www.catb.org/~esr/\">Eric S. Raymond</a>\n"},{"id":"205796","messageId":"7vfw2k8t7k.fsf@alter.siamese.dyndns.org","threadId":"32498","inReplyTo":"20130101172645.GA5506@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-01T21:54:39Z","receivedAt":"2013-01-01T21:54:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric S. Raymond\" <esr@thyrsus.com> writes:\n\n> The combination of git-cvsimport and cvsps had serious problems.\n> Among these were:\n>\n> (1) Analysis of branchy repos was buggy in multiple ways in both\n>     programs, leading to incorrect repo translations.\n>\n> (2) Even after a correct branch analysis, extra (redundant) fileops\n>     would often be generated on the new-branch side.\n>\n> (3) Inability to report more than one tag pointing to the same revision.\n>\n> (4) Failure in certain cases of clock-skew reported by the t9603 test.\n>\n> (5) Failure to use commitids for changeset ordering in cases were this\n>     would have prevented clock skew from causing incorrect grouping.\n>\n> Problems 2-5 and portions of problem 1 have been solved by a major\n> rewrite of cvsps (the 3.x release series); it now emits a git\n> fast-import stream.\n\nSo..., is this a flag-day patch?\n\nAfter this is merged, users who have been interoperating with CVS\nrepositories with the older cvsps have to install the updated cvsps\nbefore using a new version of Git that ships with it?  As long as\nthey update both cvsps and cvsimport, they can continue using the\nexisting repository to get updates from the same upstream CVS\nrepository without losing hisory continuity?\n\nI would have preferred an addition of \"git cvsimport-new\" (or rename\nof the existing one to \"git cvsimport-old\"), with additional tests\nthat compare the results of these two implemenations on simple CVS\nhistory that cvsimport-old did *not* screw up, to ensure that (1)\npeople with existing set-up can choose to keep using the old one,\nperhaps by tweaking their process to use cvsimport-old, and (2) the\nupdated one will give these people the identical conversion results,\nas long as the history they have been interacting with do not have\nthe corner cases that trigger bugs in older cvsps.\n\nOr am I being too conservative?\n"},{"id":"205819","messageId":"20130102003344.GA9651@thyrsus.com","threadId":"32498","inReplyTo":"7vfw2k8t7k.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Eric S. Raymond","fromEmail":"esr@thyrsus.com","sentAt":"2013-01-02T00:33:44Z","receivedAt":"2013-01-02T00:33:44Z","isPatch":true,"sender":{"key":"esr@thyrsus.com","avatar":"https://avatars.githubusercontent.com/u/727961?v=4"},"body":"Junio C Hamano <gitster@pobox.com>:\n> So..., is this a flag-day patch?\n> \n> After this is merged, users who have been interoperating with CVS\n> repositories with the older cvsps have to install the updated cvsps\n> before using a new version of Git that ships with it?\n\nYes, they must install an updated cvsps. But this is hardly a loss, as\nthe old version was perilously broken.\n\nThere was an error or typo in the branch-analysis code, dating from\n2006 and possibly earlier, that meant that branch root points would\nalmost always be attributed to parent patchsets one patchset earlier\nthan they should have been.  Shocked me when I found it - how was this\nmissed for six years?\n\nBecause of the way the analysis is done, this fundamental bug would\nalso cause secondary damage like file changes near the root point\ngetting attributed to the wrong branch.  In fact, this is how I\nfirst spotted the problem; my test suite exhibited this symptom.\n\nAnd mind you this is on top of ancestry-branch tracking not working -\ntwo separate bugs that could interact in ways I'd really rather not\nthink about.  The bottom line is that every import of a branchy CVS\nrepo with a pre-3.x version of cvsps is probably wrong.\n\nThe old git-cvsimport code was doing its part to screw things up, too.\nAt least three of the bugs on its manual page are problems I couldn't\nreproduce using a bare cvsps instance, even the old broken version.\n\n>                                                    As long as\n> they update both cvsps and cvsimport, they can continue using the\n> existing repository to get updates from the same upstream CVS\n> repository without losing hisory continuity?\n\nYes, but in that case I would strongly advise re-importing the entire\nCVS history, as the portion analyzed with 2.2b1 and earlier versions\nof cvsps will almost certainly have been somewhat garbled if it\ncontains any branches.\n\n> I would have preferred an addition of \"git cvsimport-new\" (or rename\n> of the existing one to \"git cvsimport-old\"), with additional tests\n> that compare the results of these two implemenations on simple CVS\n> history that cvsimport-old did *not* screw up, to ensure that (1)\n> people with existing set-up can choose to keep using the old one,\n> perhaps by tweaking their process to use cvsimport-old, and (2) the\n> updated one will give these people the identical conversion results,\n> as long as the history they have been interacting with do not have\n> the corner cases that trigger bugs in older cvsps.\n> \n> Or am I being too conservative?\n\nI think you are being too conservative.  This patch is *not* a mere\nfeature upgrade. The branch-analysis bug I found three days ago is not\na minor problem, it is a big ugly showstopper for any case beside the\nsimplest linear histories.  Only linear histories will not break.\n\n'People with existing set-ups' should absolutely *not* 'keep using the\nold one'; we should yank that choice away from them and get the old\ncvsimport/cvsps pair out of use *as fast as possible*, because it\nsilently mangles branchy imports.\n\nAccordingly, giving people the idea that it's OK to use old and new\nversions in parallel would be an extremely bad idea.  I would go so\nfar as to call it irresponsible.\n\nHere is what I have done to ease the transition:\n\nIf you try to use old git-cvsimport with new cvsps, new cvsps will detect\nthis and ship a message to stderr telling you to upgrade\n\nIf you try to use new git-cvsimport with old cvsps, old cvsps will complain\nof an invalid argument and git-cvsimport will quit.\n\nAs for testing...cvsps now has several dozen self-tests on five\ndifferent CVS repositories, including improved versions of the\nt960[123] tests.  I will keep developing these as I work on bringing\nparsecvs up to snuff.\n\nI don't think there is a lot of point in git-cvsimport having its own\ntests any more.  If you read it I think you'll see why; it's a much\nthinner wrapper around the conversion engine(s) than it used to be. In\nparticular, it no longer does its own protocol transactions to the\nCVS server.\n-- \n\t\t<a href=\"http://www.catb.org/~esr/\">Eric S. Raymond</a>\n"},{"id":"205820","messageId":"7v1ue475re.fsf@alter.siamese.dyndns.org","threadId":"32498","inReplyTo":"20130102003344.GA9651@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-02T01:06:29Z","receivedAt":"2013-01-02T01:06:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric S. Raymond\" <esr@thyrsus.com> writes:\n\n> Junio C Hamano <gitster@pobox.com>:\n>> So..., is this a flag-day patch?\n>> \n>> After this is merged, users who have been interoperating with CVS\n>> repositories with the older cvsps have to install the updated cvsps\n>> before using a new version of Git that ships with it?\n>\n> Yes, they must install an updated cvsps. But this is hardly a loss, as\n> the old version was perilously broken.\n>\n> There was an error or typo in the branch-analysis code, dating from\n> 2006 and possibly earlier, that meant that branch root points would\n> almost always be attributed to parent patchsets one patchset earlier\n> than they should have been.  Shocked me when I found it - how was this\n> missed for six years?\n\nWould it be that not many people use branchy history in CVS, as\nmerging there was soooo painful?\n\n>> Or am I being too conservative?\n>\n> I think you are being too conservative.  This patch is *not* a mere\n> feature upgrade. The branch-analysis bug I found three days ago is not\n> a minor problem, it is a big ugly showstopper for any case beside the\n> simplest linear histories.  Only linear histories will not break.\n\nThat is exactly my point.  It never worked in a branchy history, and\nthat is an indication that people who didn't complain and used the\nold cvsimport with branch-incapable cvsps happily would have been\nworking with a linear history.  Either nobody uses cvsimport in the\ndaily work, in which case a flag-day is perfectly fine, or we will\nhave many people who are forced to update to unproven version for no\nimmediate upside because the upstream repositories they work with, or\noptions they use cvsimport, do not trigger the multi-branch bug.\n\nI however do understand that updating is the only sensible thing to\ndo for them *in the longer term*, as older cvsimport and cvsps are\nno longer maintained, and sooner they update the better the chance\nthe new cvsimport becomes perfect earlier.\n\n> 'People with existing set-ups' should absolutely *not* 'keep using the\n> old one'; we should yank that choice away from them and get the old\n> cvsimport/cvsps pair out of use *as fast as possible*, because it\n> silently mangles branchy imports.\n\nI still am not convinced, especially without a \"we make sure we do\nnot regress in linear histories\" side-by-side test in place.  That\nsounds irresponsible.\n\nBut others may disagree, and I'd have to sleep on it.\n\nI'd prefer to hear from somebody who is *not* defending on his newer\nimplementation, but from somebody who is actively using cvsimport as\nan end user.  On the end-users' side, there always is this anxiety\nthat a radical rewrite will always introduce new bugs, even when\nthey know the rewrite is done very competently.\n\n> Here is what I have done to ease the transition:\n>\n> If you try to use old git-cvsimport with new cvsps, new cvsps will detect\n> this and ship a message to stderr telling you to upgrade\n\nSounds sensible.\n\n> If you try to use new git-cvsimport with old cvsps, old cvsps will complain\n> of an invalid argument and git-cvsimport will quit.\n\nWith an error message that tells the user to update cvsps, this also\nsounds sensible.\n"},{"id":"205843","messageId":"20130102080247.GA20002@elie.Belkin","threadId":"32498","inReplyTo":"20130102003344.GA9651@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-01-02T08:02:47Z","receivedAt":"2013-01-02T08:02:47Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nEric S. Raymond wrote:\n> Junio C Hamano <gitster@pobox.com>:\n\n>> So..., is this a flag-day patch?\n>>\n>> After this is merged, users who have been interoperating with CVS\n>> repositories with the older cvsps have to install the updated cvsps\n>> before using a new version of Git that ships with it?\n>\n> Yes, they must install an updated cvsps. But this is hardly a loss, as\n> the old version was perilously broken.\n\nSpeaking with my Debian packager hat on: the updated cvsps is not\navailable in Debian.  \"git cvsimport\" is, and it has users that report\nbugs from time to time.  With this change, I would either have to take\non responsibility for maintenance of the cvsps package (not going to\nhappen) or drop \"git cvsimport\".  That's a serious regression.\n\nThe moment someone takes care of packaging the updated cvsps, I'll\nstop minding, though. ;-)\n\nI wouldn't be surprised if the situation on other OSes is similar.\nThis is too early to require such a dependency.\n\nHope that helps,\nJonathan\n"},{"id":"205853","messageId":"20130102105919.GA14391@thyrsus.com","threadId":"32498","inReplyTo":"20130102080247.GA20002@elie.Belkin","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Eric S. Raymond","fromEmail":"esr@thyrsus.com","sentAt":"2013-01-02T10:59:19Z","receivedAt":"2013-01-02T10:59:19Z","isPatch":true,"sender":{"key":"esr@thyrsus.com","avatar":"https://avatars.githubusercontent.com/u/727961?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com>:\n> Speaking with my Debian packager hat on: the updated cvsps is not\n> available in Debian.  \"git cvsimport\" is, and it has users that report\n> bugs from time to time.  With this change, I would either have to take\n> on responsibility for maintenance of the cvsps package (not going to\n> happen) or drop \"git cvsimport\".  That's a serious regression.\n\nHow does going from \"it silently damages imports\" to \"it fails with\nan error message\" constitute a regression?\n \n> The moment someone takes care of packaging the updated cvsps, I'll\n> stop minding, though. ;-)\n\nI'll ping the Debian QA group.\n-- \n\t\t<a href=\"http://www.catb.org/~esr/\">Eric S. Raymond</a>\n"},{"id":"205857","messageId":"20130102153933.GA30813@elie.Belkin","threadId":"32498","inReplyTo":"20130102105919.GA14391@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-01-02T15:39:33Z","receivedAt":"2013-01-02T15:39:33Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Eric S. Raymond wrote:\n> Jonathan Nieder <jrnieder@gmail.com>:\n\n>> Speaking with my Debian packager hat on: the updated cvsps is not\n>> available in Debian.  \"git cvsimport\" is, and it has users that report\n>> bugs from time to time.  With this change, I would either have to take\n>> on responsibility for maintenance of the cvsps package (not going to\n>> happen) or drop \"git cvsimport\".  That's a serious regression.\n>\n> How does going from \"it silently damages imports\" to \"it fails with\n> an error message\" constitute a regression?\n\nThe former is already loudly advertised in the package description and\nmanpage, at least lets you get work done, and works fine for simple\nrepositories with linear history.\n\nTaking away a command that people have been using in everyday work is\npretty much a textbook example of a regression, no?\n\nHope that helps,\nJonathan\n"},{"id":"205858","messageId":"20130102161848.GA18447@thyrsus.com","threadId":"32498","inReplyTo":"20130102153933.GA30813@elie.Belkin","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Eric S. Raymond","fromEmail":"esr@thyrsus.com","sentAt":"2013-01-02T16:18:48Z","receivedAt":"2013-01-02T16:18:48Z","isPatch":true,"sender":{"key":"esr@thyrsus.com","avatar":"https://avatars.githubusercontent.com/u/727961?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com>:\n> The former is already loudly advertised in the package description and\n> manpage, at least lets you get work done, and works fine for simple\n> repositories with linear history.\n\nTwo of the three claims in this paragraph are false.  The manual page\ndoes not tell you what is true, which is that old cvsps will fuck up\nevery branch by putting the root point at the wrong place.  And if you\ncall silently and randomly damaging imports getting work done, your\ndefinitions of \"work\" and \"done\" are broken.\n\n> Taking away a command that people have been using in everyday work is\n> pretty much a textbook example of a regression, no?\n\nThat would be, but we are talking about replacing total breakage with\na git-cvsimport that actually works and that you invoke in pretty much the\nsame way as the old one.  Nothing is or will be taken away.\n\nIn any case, once the distros package cvsps 3.x, old cvsimport will terminate\nwith an error return, because cvsps-3.x sees an obsolete option that \ngit-cvsimport tries to use as a command to treminate after displaying\na prompt to upgrade.\n\nThe most we can accomplish by being \"conservative\" is to lengthen the\nwindow during which people will falsely believe that their conversion\nprocess is working.\n-- \n\t\t<a href=\"http://www.catb.org/~esr/\">Eric S. Raymond</a>\n"},{"id":"205863","messageId":"CACPiFCKDoAoKxM4YU6uKoOGcDgLbXnCoUMO5iyf-wCWXh3j70A@mail.gmail.com","threadId":"32498","inReplyTo":"20130102161848.GA18447@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Martin Langhoff","fromEmail":"martin.langhoff@gmail.com","sentAt":"2013-01-02T16:32:09Z","receivedAt":"2013-01-02T16:32:09Z","isPatch":true,"sender":{"key":"martin.langhoff@gmail.com","avatar":"https://gravatar.com/avatar/1e3f311b6c4c15836501901ca58f8c0b0667246488084ba524d8bc9867e22fd9?d=mp&s=160"},"body":"First of all, I am at the same time a sad, nostalgic, and very happy\nthat old cvsimport is getting replaced.\n\nOn Wed, Jan 2, 2013 at 11:18 AM, Eric S. Raymond <esr@thyrsus.com> wrote:\n> Two of the three claims in this paragraph are false.  The manual page\n> does not tell you what is true, which is that old cvsps will fuck up\n> every branch by putting the root point at the wrong place.  And if you\n> call silently and randomly damaging imports getting work done, your\n> definitions of \"work\" and \"done\" are broken.\n\nThe existing cvsps/cvsimport combo work for CVS repos with simple\nbranches, and can track those over time.\n\nReplacement with something more solid is welcome, but until you are\nextremely confident of its handling of legacy setups... I would still\nprovide the old cvsimport, perhaps in contrib.\n\ncheers,\n\n\n\nm\n--\n martin.langhoff@gmail.com\n martin@laptop.org -- Software Architect - OLPC\n - ask interesting questions\n - don't get distracted with shiny stuff  - working code first\n - http://wiki.laptop.org/go/User:Martinlanghoff\n"},{"id":"205862","messageId":"20130102163527.GA31196@elie.Belkin","threadId":"32498","inReplyTo":"20130102161848.GA18447@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-01-02T16:35:58Z","receivedAt":"2013-01-02T16:35:58Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Eric S. Raymond wrote:\n> Jonathan Nieder <jrnieder@gmail.com>:\n\n>> The former is already loudly advertised in the package description and\n>> manpage, at least lets you get work done, and works fine for simple\n>> repositories with linear history.\n>\n> Two of the three claims in this paragraph are false.\n\nGive me a break.\n\nAre you telling me that when multiple users read a manpage that states\n\n| WARNING: for certain situations the import leads to incorrect\n| results. Please see the section ISSUES for further reference.\n[...]\n| Problems related to timestamps:\n[...]\n| Problems related to branches:\n[...]\n| Problems related to tags:\n[...]\n| consider using these alternative tools which proved to be more\n| stable in practice:\n\nand a package description that states\n\n| The git cvsimport tool can incrementally import from a repository\n| that is being actively developed and only requires remote access\n| over CVS protocol. Unfortunately, in many situations the import\n| leads to incorrect results. For reliable, one-shot imports, cvs2git\n| from the cvs2svn package or parsecvs may be a better fit.\n\nand decide to use the tool anyway, this is not evidence that the tool\nis invaluable to them, despite its shortcomings?\n\nPerhaps the users reporting bugs didn't read the manpage and package\ndescription (despite quoting the same passages and explaining why they\nused the command nonetheless) or I should ignore the judgement calls\nthey make.\n\nConsider the following workflow:\n\n 1. Update imported project periodically using git-cvsimport\n 2. Hack, do code archaeology using \"git log -S\" and \"git bisect\",\n    etc.\n 3. Fall back to a web browser and cvsweb to confirm conclusions.\n\nYou are telling me that it is not a regression to change the workflow\nto the following:\n\n 1. Try to use git-cvsimport.\n 2. Wonder where that command went.\n\nMeanwhile Junio has already suggested a way out.  Just rename the\ncommand.\n\nHope that helps,\nJonathan\n"},{"id":"205864","messageId":"20130102164107.GA19006@thyrsus.com","threadId":"32498","inReplyTo":"CACPiFCKDoAoKxM4YU6uKoOGcDgLbXnCoUMO5iyf-wCWXh3j70A@mail.gmail.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Eric S. Raymond","fromEmail":"esr@thyrsus.com","sentAt":"2013-01-02T16:41:07Z","receivedAt":"2013-01-02T16:41:07Z","isPatch":true,"sender":{"key":"esr@thyrsus.com","avatar":"https://avatars.githubusercontent.com/u/727961?v=4"},"body":"Martin Langhoff <martin.langhoff@gmail.com>:\n> Replacement with something more solid is welcome, but until you are\n> extremely confident of its handling of legacy setups... I would still\n> provide the old cvsimport, perhaps in contrib.\n\nI am extremely confident.  I built a test suite so I could be.\n-- \n\t\t<a href=\"http://www.catb.org/~esr/\">Eric S. Raymond</a>\n"},{"id":"205865","messageId":"m2fw2j3588.fsf@igel.home","threadId":"32498","inReplyTo":"20130102161848.GA18447@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Andreas Schwab","fromEmail":"schwab@linux-m68k.org","sentAt":"2013-01-02T16:43:51Z","receivedAt":"2013-01-02T16:43:51Z","isPatch":true,"sender":{"key":"schwab@linux-m68k.org","avatar":"https://avatars.githubusercontent.com/u/2175493?v=4"},"body":"\"Eric S. Raymond\" <esr@thyrsus.com> writes:\n\n> Two of the three claims in this paragraph are false.  The manual page\n> does not tell you what is true, which is that old cvsps will fuck up\n> every branch by putting the root point at the wrong place.\n\nThat doesn't look like being a widespread problem, or more people would\nhave complained.\n\nAndreas.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5\n\"And now for something completely different.\"\n"},{"id":"205866","messageId":"CABYiQpkLze8aBZecGovBdMbxSE=kYH__KZntQDt7KwqUadUYdg@mail.gmail.com","threadId":"32498","inReplyTo":"20130102164107.GA19006@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Thomas Berg","fromEmail":"merlin66b@gmail.com","sentAt":"2013-01-02T16:48:07Z","receivedAt":"2013-01-02T16:48:07Z","isPatch":true,"sender":{"key":"merlin66b@gmail.com","avatar":null},"body":"On Wed, Jan 2, 2013 at 5:41 PM, Eric S. Raymond <esr@thyrsus.com> wrote:\n> Martin Langhoff <martin.langhoff@gmail.com>:\n>> Replacement with something more solid is welcome, but until you are\n>> extremely confident of its handling of legacy setups... I would still\n>> provide the old cvsimport, perhaps in contrib.\n>\n> I am extremely confident.  I built a test suite so I could be.\n\nI too am glad to see some work go into the cvsimport script. So just\nto clear things up, previously you said this:\n> Yes, they must install an updated cvsps.\n\nThis is the problem, and one that is easily \"solved\" by just keeping a\ncopy of the old command.\n\nRemember that for many users of these tools it doesn't matter if the\nhistory is correct or not, as long as the head checkout contains the\nright files and they are able to submit new changes. With this\ndefinition of \"works\" git-cvsimport is not that broken I think.\n\nCheers,\n- Thomas\n"},{"id":"205871","messageId":"7vr4m331bn.fsf@alter.siamese.dyndns.org","threadId":"32498","inReplyTo":"20130102003344.GA9651@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-02T18:08:12Z","receivedAt":"2013-01-02T18:08:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric S. Raymond\" <esr@thyrsus.com> writes:\n\n> If you try to use new git-cvsimport with old cvsps, old cvsps will complain\n> of an invalid argument and git-cvsimport will quit.\n\nI see an opening for smoother transition here.\n\nLike it or not, you cannot force distros to ship with cvsps 3.0 when\nwe ship our 1.8.2 (or 2.0 or whatever) that includes a cvsimport\nthat requires cvsps 3.0.  The best we can do is to make it capable\nof working with cvsps 3.0 for a better result (when available), and\nworking with cvsps 2.0 in a limited way as ever (linear history\nonly, etc. etc.) when cvsps 3.0 is not available.\n\nAs your version already knows how to detect the case where cvsps is\ntoo old to operate with it, I imagine it to be straight-forward to\nship the old cvsimport under obscure name, \"git cvsimport--old\" or\nsomething, and spawn it from your version when necessary, perhaps\nafter issuing a warning \"cvsps 3.0 not found; switching to an old\nand unmaintained version of cvsimport...\"\n\nThat way, people who have been happily working with linear CVS\nhistories with the old limited tool can keep using the same set-up\nuntil their distro update their cvsps, without harming people who\nneed to work with more complex CVS histories, who can choose to\nupdate their cvsps early themselves as $HOME/bin/cvsps earlier on\ntheir $PATH.\n\nBy \"cvsimport\" (the current version), we are talking about a piece\nof software that has been used in the field for more than 5 years,\nstill with a handful of patches to enhance it in the past two years.\nA flag-day \"this hot-off-the-press version is infinitely better\"\nreplacement is not an option, especially when we can expect that\nexisting users are not asking for an \"inifinitely better\" version\n(they rather prefer \"stable\" in the \"works just as before\" sense),\neven when the hot-off-the-press version *is* infinitely better in\nsome use cases such as dealing with branchy histories.\n"},{"id":"205872","messageId":"20130102183710.GB19006@thyrsus.com","threadId":"32498","inReplyTo":"7vr4m331bn.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Eric S. Raymond","fromEmail":"esr@thyrsus.com","sentAt":"2013-01-02T18:37:10Z","receivedAt":"2013-01-02T18:37:10Z","isPatch":true,"sender":{"key":"esr@thyrsus.com","avatar":"https://avatars.githubusercontent.com/u/727961?v=4"},"body":"Junio C Hamano <gitster@pobox.com>:\n> As your version already knows how to detect the case where cvsps is\n> too old to operate with it, I imagine it to be straight-forward to\n> ship the old cvsimport under obscure name, \"git cvsimport--old\" or\n> something, and spawn it from your version when necessary, perhaps\n> after issuing a warning \"cvsps 3.0 not found; switching to an old\n> and unmaintained version of cvsimport...\"\n\nThis can be done.  As this may not be the last case in which it comes up,\nperhaps we should have an 'obsolete' directory distinct from 'contrib'.\n\nI'll ship another patch.\n-- \n\t\t<a href=\"http://www.catb.org/~esr/\">Eric S. Raymond</a>\n"},{"id":"205875","messageId":"7vmwwr2yki.fsf@alter.siamese.dyndns.org","threadId":"32498","inReplyTo":"20130102183710.GB19006@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-02T19:07:41Z","receivedAt":"2013-01-02T19:07:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Eric S. Raymond\" <esr@thyrsus.com> writes:\n\n> Junio C Hamano <gitster@pobox.com>:\n>> As your version already knows how to detect the case where cvsps is\n>> too old to operate with it, I imagine it to be straight-forward to\n>> ship the old cvsimport under obscure name, \"git cvsimport--old\" or\n>> something, and spawn it from your version when necessary, perhaps\n>> after issuing a warning \"cvsps 3.0 not found; switching to an old\n>> and unmaintained version of cvsimport...\"\n>\n> This can be done.  As this may not be the last case in which it comes up,\n> perhaps we should have an 'obsolete' directory distinct from 'contrib'.\n>\n> I'll ship another patch.\n\nAlright; thanks.\n\nDon't forget to sign-off your patch ;-)\n"},{"id":"205884","messageId":"CACPiFCKNkpaf6CgU=5rn1dyUSG2KV43oeTKJgRsSh9-Rhtq3Kw@mail.gmail.com","threadId":"32498","inReplyTo":"20130102164107.GA19006@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Martin Langhoff","fromEmail":"martin.langhoff@gmail.com","sentAt":"2013-01-02T21:15:31Z","receivedAt":"2013-01-02T21:15:31Z","isPatch":true,"sender":{"key":"martin.langhoff@gmail.com","avatar":"https://gravatar.com/avatar/1e3f311b6c4c15836501901ca58f8c0b0667246488084ba524d8bc9867e22fd9?d=mp&s=160"},"body":"On Wed, Jan 2, 2013 at 11:41 AM, Eric S. Raymond <esr@thyrsus.com> wrote:\n> Martin Langhoff <martin.langhoff@gmail.com>:\n>> Replacement with something more solid is welcome, but until you are\n>> extremely confident of its handling of legacy setups... I would still\n>> provide the old cvsimport, perhaps in contrib.\n>\n> I am extremely confident.  I built a test suite so I could be.\n\nThis is rather off-putting. Really.\n\nI dealt with enough CVS repos to see that the branch point could be\nambiguous, and that some cases were incurably ugly and ambiguous.\n\nOff the top of my head I can recall\n\n - Files created on a branch appear on HEAD (if the cvs client was\nwell behaved, in HEAD's attic, if the cvs client was buggy... )\n\n - Files tagged with the branch at a much later time. Scenario is a\ndeveloper opening/tagging a new branch mindlessly on a partial\ncheckout; then trying to \"fix\" the problem later.\n\nMy best guess is that you haven't dealt with enough ugly CVS repos. I\nused to have the old original X.org repos, but no more. Surely\nMozilla's fugly old CVS repos are up somewhere, and may be\ntherapeutic.\n\ncheers,\n\n\n\nm\n--\n martin.langhoff@gmail.com\n martin@laptop.org -- Software Architect - OLPC\n - ask interesting questions\n - don't get distracted with shiny stuff  - working code first\n - http://wiki.laptop.org/go/User:Martinlanghoff\n"},{"id":"205888","messageId":"20130102222849.GA21105@thyrsus.com","threadId":"32498","inReplyTo":"CACPiFCKNkpaf6CgU=5rn1dyUSG2KV43oeTKJgRsSh9-Rhtq3Kw@mail.gmail.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Eric S. Raymond","fromEmail":"esr@thyrsus.com","sentAt":"2013-01-02T22:28:49Z","receivedAt":"2013-01-02T22:28:49Z","isPatch":true,"sender":{"key":"esr@thyrsus.com","avatar":"https://avatars.githubusercontent.com/u/727961?v=4"},"body":"Martin Langhoff <martin.langhoff@gmail.com>:\n> I dealt with enough CVS repos to see that the branch point could be\n> ambiguous, and that some cases were incurably ugly and ambiguous.\n\nYou are quite right, but you have misintepreted the subject of my\nconfidence.  I am under no illusion that the new cvsimport/cvsps \npair is a perfect solution to the CVS-lifting problem, nor even that\nsuch a solution is possible.\n\n> My best guess is that you haven't dealt with enough ugly CVS repos. I\n> used to have the old original X.org repos, but no more. Surely\n> Mozilla's fugly old CVS repos are up somewhere, and may be\n> therapeutic.\n\nThanks, but since I wrote reposurgeon in 2010 I've done more conversions\nof messy CVS and Subversion repositories than I can easily remember (the\nSubversion ones being relevant because they often have truly nasty CVS\nartifacts in their early history).  Just off the top of my head there's\nbeen gpsd, the Network Utility Tools, Roundup, SSTK2000, the Hercules \nproject, and robotfindskitten.  And a raft of smaller projects - I sought\nthem out as torture tests for reposurgeon.\n\nI am therefore intimately, painfully familiar with how bad CVS repos\ncan get.  I take it as given that there are still boojums that will\nperplex my tools lurking out there in the unexplored jungle.\n\nIn fact, this very kind of prior experience had been a major\nmotivation for reposurgeon.  I became convinced several years back\nthat the batchy design philosophy of conventional repo-conversion\ntools was flawed, not flexible enough to deal with the real-world\nmesses out there.  So I wrote reposurgeon to amplify human judgment\nrather than try to replace it.\n \nAn example of the batchiness mistake close to home is the -m and -M\noptions in the old version of cvsimport.  It takes human judgment\nlooking at the whole commit DAG in gitspace to decide what merge\npoints would best express the (as you say, sometimes ambiguous) CVS\nhistory - what's needed is a scalpel and sutures in a surgeon's hand,\nnot a regexp hammer.\n\nFor extended discussion, see my blog post \"Repositories In\nTranslation\" at http://esr.ibiblio.org/?p=3859 in which I argue that\nthe process has much more in common with the ambiguity of literary\ntranslation than is normally understood.\n\nNo, what I am very confident about is the performance and stability of\nthe new cvsps/cvsimport code on *the cases the old code handled* - and\na fairly well-defined larger group of many more cases.\n\nMy confidence is derived from having built a test suite that\nincorporates and improves on the git-tree tests. I don't have to merely\nguess or hope that the new code works better, I can exhibit tests\nthat demonstrate it.\n\nAmong my near-term to-do items are applying those tests to cvs2git and\nparsecvs.  But I first need to get parsecvs working again; presently, as I've\ninherited it, it does not correctly create a HEAD reference in the\ntranslated git repo.\n-- \n\t\t<a href=\"http://www.catb.org/~esr/\">Eric S. Raymond</a>\n"},{"id":"205899","messageId":"CACPiFCLz0wuG1qC4UmQ4tj4s9ftXoh4q=PUBkxtKh05bnu3coA@mail.gmail.com","threadId":"32498","inReplyTo":"20130102222849.GA21105@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Martin Langhoff","fromEmail":"martin.langhoff@gmail.com","sentAt":"2013-01-02T23:44:24Z","receivedAt":"2013-01-02T23:44:24Z","isPatch":true,"sender":{"key":"martin.langhoff@gmail.com","avatar":"https://gravatar.com/avatar/1e3f311b6c4c15836501901ca58f8c0b0667246488084ba524d8bc9867e22fd9?d=mp&s=160"},"body":"On Wed, Jan 2, 2013 at 5:28 PM, Eric S. Raymond <esr@thyrsus.com> wrote:\n> Martin Langhoff <martin.langhoff@gmail.com>:\n>> I dealt with enough CVS repos to see that the branch point could be\n>> ambiguous, and that some cases were incurably ugly and ambiguous.\n>\n> You are quite right, but you have misintepreted the subject of my\n> confidence.  I am under no illusion that the new cvsimport/cvsps\n> pair is a perfect solution to the CVS-lifting problem, nor even that\n> such a solution is possible.\n\nThanks. That is a much more reassuring stance.\n\n\n\nm\n--\n martin.langhoff@gmail.com\n martin@laptop.org -- Software Architect - OLPC\n - ask interesting questions\n - don't get distracted with shiny stuff  - working code first\n - http://wiki.laptop.org/go/User:Martinlanghoff\n"},{"id":"205910","messageId":"CAEUsAPYwinmbDkSVu71WJRgUjLfBeNdKDFt6O1f8-Ti9evn6Hw@mail.gmail.com","threadId":"32498","inReplyTo":"20130101172645.GA5506@thyrsus.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2013-01-03T06:34:52Z","receivedAt":"2013-01-03T06:34:52Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"On Tue, Jan 1, 2013 at 11:26 AM, Eric S. Raymond <esr@thyrsus.com> wrote:\n> diff --git a/git-cvsimport.py b/git-cvsimport.py\n> new file mode 100755\n> index 0000000..6407e8a\n> --- /dev/null\n> +++ b/git-cvsimport.py\n> @@ -0,0 +1,342 @@\n> +#!/usr/bin/env python\n> +#\n> +# Import CVS history into git\n> +#\n> +# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.\n> +#\n> +# By Eric S. Raymond <esr@thyrsus.com>, December 2012\n> +# May be redistributed under the license of the git project.\n> +\n> +import sys\n> +\n> +if sys.hexversion < 0x02060000:\n> +    sys.stderr.write(\"git cvsimport: requires Python 2.6 or later.\\n\")\n> +    sys.exit(1)\n> +\n> +import os, getopt, subprocess, tempfile, shutil\n> +\n> +DEBUG_COMMANDS = 1\n> +\n> +class Fatal(Exception):\n> +    \"Unrecoverable error.\"\n> +    def __init__(self, msg):\n> +        Exception.__init__(self)\n> +        self.msg = msg\n> +\n> +def do_or_die(dcmd, legend=\"\"):\n> +    \"Either execute a command or raise a fatal exception.\"\n> +    if legend:\n> +        legend = \" \"  + legend\n> +    if verbose >= DEBUG_COMMANDS:\n> +        sys.stdout.write(\"git cvsimport: executing '%s'%s\\n\" % (dcmd, legend))\n> +    try:\n> +        retcode = subprocess.call(dcmd, shell=True)\n> +        if retcode < 0:\n> +            raise Fatal(\"git cvsimport: child was terminated by signal %d.\" % -retcode)\n> +        elif retcode != 0:\n> +            raise Fatal(\"git cvsimport: child returned %d.\" % retcode)\n> +    except (OSError, IOError) as e:\n> +        raise Fatal(\"git cvsimport: execution of %s%s failed: %s\" % (dcmd, legend, e))\n> +\n> +def capture_or_die(dcmd, legend=\"\"):\n> +    \"Either execute a command and capture its output or die.\"\n> +    if legend:\n> +        legend = \" \"  + legend\n> +    if verbose >= DEBUG_COMMANDS:\n> +        sys.stdout.write(\"git cvsimport: executing '%s'%s\\n\" % (dcmd, legend))\n> +    try:\n> +        return subprocess.check_output(dcmd, shell=True)\n> +    except subprocess.CalledProcessError as e:\n> +        if e.returncode < 0:\n> +            sys.stderr.write(\"git cvsimport: child was terminated by signal %d.\" % -e.returncode)\n> +        elif e.returncode != 0:\n> +            sys.stderr.write(\"git cvsimport: child returned %d.\" % e.returncode)\n> +        sys.exit(1)\n> +\n> +class cvsps:\n> +    \"Method class for cvsps back end.\"\n> +    def __init__(self):\n> +        self.opts = \"\"\n> +        self.revmap = None\n> +    def set_repo(self, val):\n> +        \"Set the repository root option.\"\n> +        if not val.startswith(\":\"):\n> +            if not val.startswith(os.sep):\n> +                val = os.path.abspath(val)\n> +            val = \":local:\" + val\n> +        self.opts += \" --root '%s'\" % val\n> +    def set_authormap(self, val):\n> +        \"Set the author-map file.\"\n> +        self.opts += \" -A '%s'\" % val\n> +    def set_fuzz(self, val):\n> +        \"Set the commit-similarity window.\"\n> +        self.opts += \" -z %s\" % val\n> +    def set_nokeywords(self):\n> +        \"Suppress CVS keyword expansion.\"\n> +        self.opts += \" -k\"\n> +    def add_opts(self, val):\n> +        \"Add options to the engine command line.\"\n> +        self.opts += \" \" + val\n> +    def set_exclusion(self, val):\n> +        \"Set a file exclusion regexp.\"\n> +        self.opts += \" -n -f '%s'\" % val\n> +    def set_after(self, val):\n> +        \"Set a date threshold for incremental import.\"\n> +        self.opts += \" -d '%s'\" % val\n> +    def set_revmap(self, val):\n> +        \"Set the file to which the engine should dump a reference map.\"\n> +        self.revmap = val\n> +        self.opts += \" -R '%s'\" % self.revmap\n> +    def set_module(self, val):\n> +        \"Set the module to query.\"\n> +        self.opts += \" \" + val\n> +    def command(self):\n> +        \"Emit the command implied by all previous options.\"\n> +        return \"cvsps --fast-export \" + self.opts\n> +\n> +class cvs2git:\n> +    \"Method class for cvs2git back end.\"\n> +    def __init__(self):\n> +        self.opts = \"\"\n> +        self.modulepath = \".\"\n> +    def set_authormap(self, _val):\n> +        \"Set the author-map file.\"\n> +        sys.stderr.write(\"git cvsimport: author maping is not supported with cvs2git.\\n\")\n> +        sys.exit(1)\n> +    def set_repo(self, _val):\n> +        \"Set the repository root option.\"\n> +        sys.stderr.write(\"git cvsimport: cvs2git must run within a repository checkout directory.\\n\")\n> +        sys.exit(1)\n> +    def set_fuzz(self, _val):\n> +        \"Set the commit-similarity window.\"\n> +        sys.stderr.write(\"git cvsimport: fuzz setting is not supported with cvs2git.\\n\")\n> +        sys.exit(1)\n> +    def set_nokeywords(self):\n> +        \"Suppress CVS keyword expansion.\"\n> +        self.opts += \" --keywords-off\"\n> +    def add_opts(self, val):\n> +        \"Add options to the engine command line.\"\n> +        self.opts += \" \" + val\n> +    def set_exclusion(self, val):\n> +        \"Set a file exclusion regexp.\"\n> +        self.opts += \" --exclude='%s'\" % val\n> +    def set_after(self, _val):\n> +        \"Set a date threshold for incremental import.\"\n> +        sys.stderr.write(\"git cvsimport: incremental import is not supported with cvs2git.\\n\")\n> +        sys.exit(1)\n> +    def set_revmap(self, _val):\n> +        \"Set the file to which the engine should dump a reference map.\"\n> +        sys.stderr.write(\"git cvsimport: can't get a reference map from cvs2git.\\n\")\n> +        sys.exit(1)\n> +    def set_module(self, val):\n> +        \"Set the module to query.\"\n> +        self.modulepath = \" \" + val\n> +    def command(self):\n> +        \"Emit the command implied by all previous options.\"\n> +        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)\n> +\n> +class filesource:\n> +    \"Method class for file-source back end.\"\n> +    def __init__(self, filename):\n> +        self.filename = filename\n> +    def __complain(self, legend):\n> +        sys.stderr.write(\"git cvsimport: %s with file source.\\n\" % legend)\n> +        sys.exit(1)\n> +    def set_repo(self, _val):\n> +        \"Set the repository root option.\"\n> +        self.__complain(\"repository can't be set\")\n> +    def set_authormap(self, _val):\n> +        \"Set the author-map file.\"\n> +        sys.stderr.write(\"git cvsimport: author maping is not supported with filesource.\\n\")\n> +        sys.exit(1)\n> +    def set_fuzz(self, _val):\n> +        \"Set the commit-similarity window.\"\n> +        self.__complain(\"fuzz can't be set\")\n> +    def set_nokeywords(self, _val):\n> +        \"Suppress CVS keyword expansion.\"\n> +        self.__complain(\"keyword suppression can't be set\")\n> +    def add_opts(self, _val):\n> +        \"Add options to the engine command line.\"\n> +        self.__complain(\"other options can't be set\")\n> +    def set_exclusion(self, _val):\n> +        \"Set a file exclusion regexp.\"\n> +        self.__complain(\"exclusions can't be set\")\n> +    def set_after(self, _val):\n> +        \"Set a date threshold for incremental import.\"\n> +        pass\n> +    def set_revmap(self, _val):\n> +        \"Set the file to which the engine should dump a reference map.\"\n> +        sys.stderr.write(\"git cvsimport: can't get a reference map from cvs2git.\\n\")\n> +        sys.exit(1)\n> +    def set_module(self, _val):\n> +        \"Set the module to query.\"\n> +        self.__complain(\"module can't be set\")\n> +    def command(self):\n> +        \"Emit the command implied by all previous options.\"\n> +        return \"cat \" + self.filename\n> +\n> +if __name__ == '__main__':\n> +    if sys.hexversion < 0x02060000:\n> +        sys.stderr.write(\"git cvsimport: requires Python 2.6 or later.\\n\")\n> +        sys.exit(1)\n> +    (options, arguments) = getopt.getopt(sys.argv[1:], \"vbe:d:C:r:o:ikus:p:z:P:S:aL:A:Rh\")\n> +    verbose = 0\n> +    bare = False\n> +    root = None\n> +    outdir = os.getcwd()\n> +    remotize = False\n> +    import_only = False\n> +    underscore_to_dot = False\n> +    slashsubst = None\n> +    authormap = None\n> +    revisionmap = False\n> +    backend = cvsps()\n> +    for (opt, val) in options:\n> +        if opt == '-v':\n> +            verbose += 1\n> +        elif opt == '-b':\n> +            bare = True\n> +        elif opt == '-e':\n> +            for cls in (cvsps, cvs2git):\n> +                if cls.__name__ == val:\n> +                    backend = cls()\n> +                    break\n> +            else:\n> +                sys.stderr.write(\"git cvsimport: unknown engine %s.\\n\" % val)\n> +                sys.exit(1)\n> +        elif opt == '-d':\n> +            backend.set_repo(val)\n> +        elif opt == '-C':\n> +            outdir = val\n> +        elif opt == '-r':\n> +            remotize = True\n> +        elif opt == '-o':\n> +            sys.stderr.write(\"git cvsimport: -o is no longer supported.\\n\")\n> +            sys.exit(1)\n> +        elif opt == '-i':\n> +            import_only = True\n> +        elif opt == '-k':\n> +            backend.set_nokeywords()\n> +        elif opt == '-u':\n> +            underscore_to_dot = True\n> +        elif opt == '-s':\n> +            slashsubst = val\n> +        elif opt == '-p':\n> +            backend.add_opts(val.replace(\",\", \" \"))\n> +        elif opt == '-z':\n> +            backend.set_fuzz(val)\n> +        elif opt == '-P':\n> +            backend = filesource(val)\n> +            sys.exit(1)\n> +        elif opt in ('-m', '-M'):\n> +            sys.stderr.write(\"git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\\n\")\n> +            sys.exit(1)\n> +        elif opt == '-S':\n> +            backend.set_exclusion(val)\n> +        elif opt == '-a':\n> +            sys.stderr.write(\"git cvsimport: -a is no longer supported.\\n\")\n> +            sys.exit(1)\n> +        elif opt == '-L':\n> +            sys.stderr.write(\"git cvsimport: -L is no longer supported.\\n\")\n> +            sys.exit(1)\n> +        elif opt == '-A':\n> +            authormap = os.path.abspath(val)\n> +        elif opt == '-R':\n> +            revisionmap = True\n> +        else:\n> +            print \"\"\"\\\n> +git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>]\n> +     [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>]\n> +     [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>]\n> +     [<CVS_module>]\n> +\"\"\"\n> +\n> +    def metadata(fn):\n> +        if bare:\n> +            return fn\n> +        else:\n> +            return os.path.join(\".git\", fn)\n> +    try:\n> +        if outdir:\n> +            try:\n> +                # If the output directory does not exist, create it\n> +                # and initialize it as a git repository.\n> +                os.mkdir(outdir)\n> +                do_or_die(\"git init --quiet \" + outdir)\n> +            except OSError:\n> +                # Otherwise, assume user wants incremental import.\n> +                if not bare and not os.path.exists(os.path.join(outdir, \".git\")):\n> +                    raise Fatal(\"output directory is not a git repository\")\n> +                threshold = capture_or_die(\"git log -1 --format=%ct\").strip()\n> +                backend.set_after(threshold)\n> +        if revisionmap:\n> +            backend.set_revmap(tempfile.mkstemp()[1])\n> +            markmap = tempfile.mkstemp()[1]\n> +        if arguments:\n> +            backend.set_module(arguments[0])\n> +        gitopts = \"\"\n> +        if bare:\n> +            gitopts += \" --bare\"\n> +        if revisionmap:\n> +            gitopts += \" --export-marks='%s'\" % markmap\n> +        if authormap:\n> +            shutil.copyfile(authormap, metadata(\"cvs_authors\"))\n> +        if os.path.exists(metadata(\"cvs-authors\")):\n> +            backend.set_authormap(metadata(\"cvs-authors\"))\n> +        do_or_die(\"%s | (cd %s >/dev/null; git fast-import --quiet %s)\" \\\n> +                  % (backend.command(), outdir, gitopts))\n\noutdir needs to be quoted in the formatted string, i.e.:\n\n  \"%s | (cd '%s' >/dev/null ...\"\n\nAlso, I noticed the generated cvs-revisions file now maps cvs\nrevisions to blobs instead of commits.  Was this change intentional?\n\nThanks,\n\nChris\n"},{"id":"205912","messageId":"7vmwwqyc8w.fsf@alter.siamese.dyndns.org","threadId":"32498","inReplyTo":"CAEUsAPYwinmbDkSVu71WJRgUjLfBeNdKDFt6O1f8-Ti9evn6Hw@mail.gmail.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-03T07:08:47Z","receivedAt":"2013-01-03T07:08:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Chris Rorvick <chris@rorvick.com> writes:\n\n> outdir needs to be quoted in the formatted string, i.e.:\n>\n>   \"%s | (cd '%s' >/dev/null ...\"\n\nThe issue is real, but I am afraid that the above is not sufficient\nbecause outdir can contain single quotes.  I think other places that\ncall out to external processes share the same issue of being careless\nabout quoting in general.\n\nDoesn't Python come with a standard subprocess module that lets you\nspawn external programs safely, similar to the way Perl's list form\nopen(), e.g. \"open($fh, \"-|\", 'git', @args)\", works?\n"},{"id":"205914","messageId":"CALWbr2xx0beca_LUHO45pGMZ4Y0jZ9-iMWq8WBO6PmW==Ysw=A@mail.gmail.com","threadId":"32498","inReplyTo":"7vmwwqyc8w.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Antoine Pelisse","fromEmail":"apelisse@gmail.com","sentAt":"2013-01-03T07:47:56Z","receivedAt":"2013-01-03T07:47:56Z","isPatch":true,"sender":{"key":"apelisse@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1929644?v=4"},"body":"> Doesn't Python come with a standard subprocess module that lets you\n> spawn external programs safely, similar to the way Perl's list form\n> open(), e.g. \"open($fh, \"-|\", 'git', @args)\", works?\n\nYou mean something like this:\n\n  p1 = subprocess.Popen([backend.command()], stdout=subprocess.PIPE)\n  subprocess.Popen([\"git\", \"fast-import\", \"--quiet\"] + gitopts,\ncwd=outdir, stdin=p1.stdout)\n\nAssuming gitopts is a list rather than a string. (care must be taken\nwith backend.command() also)\n"},{"id":"205930","messageId":"7vip7expd8.fsf@alter.siamese.dyndns.org","threadId":"32498","inReplyTo":"CALWbr2xx0beca_LUHO45pGMZ4Y0jZ9-iMWq8WBO6PmW==Ysw=A@mail.gmail.com","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-01-03T15:22:59Z","receivedAt":"2013-01-03T15:22:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Antoine Pelisse <apelisse@gmail.com> writes:\n\n>> Doesn't Python come with a standard subprocess module that lets you\n>> spawn external programs safely, similar to the way Perl's list form\n>> open(), e.g. \"open($fh, \"-|\", 'git', @args)\", works?\n\n... and of course a more boring \"system('git', $subcmd, @args)\", as well.\n\n> You mean something like this:\n>\n>   p1 = subprocess.Popen([backend.command()], stdout=subprocess.PIPE)\n>   subprocess.Popen([\"git\", \"fast-import\", \"--quiet\"] + gitopts,\n> cwd=outdir, stdin=p1.stdout)\n>\n> Assuming gitopts is a list rather than a string. (care must be taken\n> with backend.command() also)\n\nYes.\n\nI vaguely recall that the subprocess module once used to be one\nportability issue but that was between Python 2.3 and 2.4 or some\nancient history, and it should no longer be relevant.\n"},{"id":"205937","messageId":"50E5B0B2.7060205@alum.mit.edu","threadId":"32498","inReplyTo":"7vip7expd8.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] Replace git-cvsimport with a rewrite that fixes major bugs.","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-01-03T16:24:18Z","receivedAt":"2013-01-03T16:24:18Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 01/03/2013 04:22 PM, Junio C Hamano wrote:\n> Antoine Pelisse <apelisse@gmail.com> writes:\n> \n>>> Doesn't Python come with a standard subprocess module that lets you\n>>> spawn external programs safely, similar to the way Perl's list form\n>>> open(), e.g. \"open($fh, \"-|\", 'git', @args)\", works?\n> \n> ... and of course a more boring \"system('git', $subcmd, @args)\", as well.\n\nPython's os.system() takes exactly one argument, which must be a string,\nand executes it in a subshell.  subprocess is indeed the way to go.\n\n>> You mean something like this:\n>>\n>>   p1 = subprocess.Popen([backend.command()], stdout=subprocess.PIPE)\n>>   subprocess.Popen([\"git\", \"fast-import\", \"--quiet\"] + gitopts,\n>> cwd=outdir, stdin=p1.stdout)\n>>\n>> Assuming gitopts is a list rather than a string. (care must be taken\n>> with backend.command() also)\n> \n> Yes.\n> \n> I vaguely recall that the subprocess module once used to be one\n> portability issue but that was between Python 2.3 and 2.4 or some\n> ancient history, and it should no longer be relevant.\n\nsubprocess was added in Python 2.4, and the above example should work\nfine in any version >= 2.4.  But please note that other functions have\nbeen added to the module since then, like check_call() (v2.5),\ncheck_output (v2.7), and some methods were added to the Popen object in\nv2.6.\n\nSuch things are documented pretty reliably in the Python library\ndocumentation [1]; when in doubt, one can view older versions of the\nlibrary documentation, which are all available online [2].\n\nMichael\n\n[1] http://docs.python.org/2/library/\n[2] http://www.python.org/doc/versions/\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"}]}