All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
@ 2013-01-11  3:32 Junio C Hamano
  2013-01-11 16:31 ` Junio C Hamano
                   ` (2 more replies)
  0 siblings, 3 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-11  3:32 UTC (permalink / raw)
  To: git; +Cc: Eric S. Raymond

From: "Eric S. Raymond" <esr@thyrsus.com>

The combination of git-cvsimport and cvsps had serious problems.

Among these were:

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

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

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

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

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

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

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

The old Perl git-cvsimport is moved to git-cvsimport-fallback; new
git-cvsimport will hand off to the fallback script when it detects
that the user has a pre-3.x cvsps.  The fallback is only there so that
people with simple enough repositories that can be correctly handled
by cvsps-2.x but without the bleeding-edge cvsps 3.x installed does
not have to be left without any cvsimport that works for them (with
the same bugs and all).  The fallback support will be removed after
cvsps 3.x and the rewritten cvsimport matures and gets widely
available.

This patch also removes Michael Haggerty's git-cvsimport tests
(t960[123]) from the git tree.  These are actually conversion-engine
tests and have been merged into a larger cvsps test suite, which I
intend to spin out into a general CVS-lifting test that can also be
applied to utilities such as cvs2git and parsecvs.  The t9604 test
will move in a future patch, when I likewise have it integrated
into the general test suite.

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

I plan to address these problems. This patch at least gets the
cvsps-3.x/git-cvsimport combination to a state that is not too
broken to ship - that is, in all failure cases known to me it
now emits useful warnings rather than silently botching the
import.

Signed-off-by: Eric S. Raymond <esr@thyrsus.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * This was (re)sent privately to me; the previous attempt by Eric
   somehow did not reach the list.  Let me see if I have better
   luck, as we really would want to be reviewing these patches.

   I rewrote one paragraph in the log about the fallback, though.
   For the record the original read like this:

     The old Perl git-cvsimport is moved to git-cvsimport-fallback; new
     git-cvsimport will hand off to the fallback script when it detects
     that the user has a pre-3.x cvsps.  I make no warranties that
     cvsps-2.x will actually work or do anything more useful than make
     demons fly out of your nose; the fallback is only there because Junio
     disliked the idea of a flag day, and should be removed after a decent
     interval.

   which I felt somewhat irresponsible to existing users.

 Makefile                                           |   3 +-
 git-cvsimport.perl => git-cvsimport-fallback.perl  |   8 +
 git-cvsimport.py                                   | 354 +++++++++++++++++++++
 t/t9601/cvsroot/.gitattributes                     |   1 -
 t/t9601/cvsroot/CVSROOT/.gitignore                 |   2 -
 t/t9601/cvsroot/module/added-imported.txt,v        |  44 ---
 t/t9601/cvsroot/module/imported-anonymously.txt,v  |  42 ---
 .../module/imported-modified-imported.txt,v        |  76 -----
 t/t9601/cvsroot/module/imported-modified.txt,v     |  59 ----
 t/t9601/cvsroot/module/imported-once.txt,v         |  43 ---
 t/t9601/cvsroot/module/imported-twice.txt,v        |  60 ----
 t/t9602/README                                     |  62 ----
 t/t9602/cvsroot/.gitattributes                     |   1 -
 t/t9602/cvsroot/CVSROOT/.gitignore                 |   2 -
 t/t9602/cvsroot/module/default,v                   | 102 ------
 t/t9602/cvsroot/module/sub1/default,v              | 102 ------
 t/t9602/cvsroot/module/sub1/subsubA/default,v      | 101 ------
 t/t9602/cvsroot/module/sub1/subsubB/default,v      | 107 -------
 .../module/sub2/Attic/branch_B_MIXED_only,v        |  59 ----
 t/t9602/cvsroot/module/sub2/default,v              | 102 ------
 t/t9602/cvsroot/module/sub2/subsubA/default,v      | 102 ------
 t/t9602/cvsroot/module/sub3/default,v              | 102 ------
 t/t9603/cvsroot/.gitattributes                     |   1 -
 t/t9603/cvsroot/CVSROOT/.gitignore                 |   2 -
 t/t9603/cvsroot/module/a,v                         |  74 -----
 t/t9603/cvsroot/module/b,v                         |  90 ------
 26 files changed, 364 insertions(+), 1337 deletions(-)
 rename git-cvsimport.perl => git-cvsimport-fallback.perl (98%)
 create mode 100755 git-cvsimport.py
 delete mode 100644 t/t9601/cvsroot/.gitattributes
 delete mode 100644 t/t9601/cvsroot/CVSROOT/.gitignore
 delete mode 100644 t/t9601/cvsroot/module/added-imported.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-anonymously.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-modified-imported.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-modified.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-once.txt,v
 delete mode 100644 t/t9601/cvsroot/module/imported-twice.txt,v
 delete mode 100644 t/t9602/README
 delete mode 100644 t/t9602/cvsroot/.gitattributes
 delete mode 100644 t/t9602/cvsroot/CVSROOT/.gitignore
 delete mode 100644 t/t9602/cvsroot/module/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub1/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub1/subsubA/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub1/subsubB/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v
 delete mode 100644 t/t9602/cvsroot/module/sub2/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub2/subsubA/default,v
 delete mode 100644 t/t9602/cvsroot/module/sub3/default,v
 delete mode 100644 t/t9603/cvsroot/.gitattributes
 delete mode 100644 t/t9603/cvsroot/CVSROOT/.gitignore
 delete mode 100644 t/t9603/cvsroot/module/a,v
 delete mode 100644 t/t9603/cvsroot/module/b,v

diff --git a/Makefile b/Makefile
index 736ecd4..ca5d9e9 100644
--- a/Makefile
+++ b/Makefile
@@ -464,7 +464,7 @@ SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
-SCRIPT_PERL += git-cvsimport.perl
+SCRIPT_PERL += git-cvsimport-fallback.perl
 SCRIPT_PERL += git-cvsserver.perl
 SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
@@ -472,6 +472,7 @@ SCRIPT_PERL += git-svn.perl
 
 SCRIPT_PYTHON += git-remote-testgit.py
 SCRIPT_PYTHON += git-p4.py
+SCRIPT_PYTHON += git-cvsimport.py
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
 	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
diff --git a/git-cvsimport.perl b/git-cvsimport-fallback.perl
similarity index 98%
rename from git-cvsimport.perl
rename to git-cvsimport-fallback.perl
index 0a31ebd..4bc0717 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport-fallback.perl
@@ -1,4 +1,8 @@
 #!/usr/bin/perl
+# This code became obsolete in January 2013, and is retained only as a
+# fallback from git-cvsimport.py for users who have only cvsps-2.x.
+# It (and the code in cvsimport.py that calls it) should be removed
+# once the 3.x version has had a reasonable time to propagate.
 
 # This tool is copyright (c) 2005, Matthias Urlichs.
 # It is released under the Gnu Public License, version 2.
@@ -27,6 +31,10 @@
 use POSIX qw(strftime tzset dup2 ENOENT);
 use IPC::Open2;
 
+print(STDERR "You do not appear to have cvsps 3.x.\n");
+print(STDERR "Falling back to unmaintained Perl cvsimport for cvsps 2.x.\n");
+print(STDERR "Upgrade your cvsps for best results.\n");
+
 $SIG{'PIPE'}="IGNORE";
 set_timezone('UTC');
 
diff --git a/git-cvsimport.py b/git-cvsimport.py
new file mode 100755
index 0000000..129471e
--- /dev/null
+++ b/git-cvsimport.py
@@ -0,0 +1,354 @@
+#!/usr/bin/env python
+#
+# Import CVS history into git
+#
+# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.
+#
+# By Eric S. Raymond <esr@thyrsus.com>, December 2012
+# May be redistributed under the license of the git project.
+
+import sys
+
+if sys.hexversion < 0x02060000:
+    sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+    sys.exit(1)
+
+import os, getopt, subprocess, tempfile, shutil
+
+DEBUG_COMMANDS = 1
+
+class Fatal(Exception):
+    "Unrecoverable error."
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def do_or_die(dcmd, legend=""):
+    "Either execute a command or raise a fatal exception."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        retcode = subprocess.call(dcmd, shell=True)
+        if retcode < 0:
+            raise Fatal("git cvsimport: child was terminated by signal %d." % -retcode)
+        elif retcode != 0:
+            raise Fatal("git cvsimport: child returned %d." % retcode)
+    except (OSError, IOError) as e:
+        raise Fatal("git cvsimport: execution of %s%s failed: %s" % (dcmd, legend, e))
+
+def capture_or_die(dcmd, legend=""):
+    "Either execute a command and capture its output or die."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        return subprocess.check_output(dcmd, shell=True)
+    except subprocess.CalledProcessError as e:
+        if e.returncode < 0:
+            sys.stderr.write("git cvsimport: child was terminated by signal %d." % -e.returncode)
+        elif e.returncode != 0:
+            sys.stderr.write("git cvsimport: child returned %d." % e.returncode)
+        sys.exit(1)
+
+class cvsps:
+    "Method class for cvsps back end."
+    def __init__(self):
+        self.opts = ""
+        self.revmap = None
+    def set_repo(self, val):
+        "Set the repository root option."
+        if not val.startswith(":"):
+            if not val.startswith(os.sep):
+                val = os.path.abspath(val)
+            val = ":local:" + val
+        self.opts += " --root '%s'" % val
+    def set_authormap(self, val):
+        "Set the author-map file."
+        self.opts += " -A '%s'" % val
+    def set_fuzz(self, val):
+        "Set the commit-similarity window."
+        self.opts += " -z %s" % val
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " -k"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " -n -f '%s'" % val
+    def set_after(self, val):
+        "Set a date threshold for incremental import."
+        self.opts += " -d '%s'" % val
+    def set_revmap(self, val):
+        "Set the file to which the engine should dump a reference map."
+        self.revmap = val
+        self.opts += " -R '%s'" % self.revmap
+    def set_module(self, val):
+        "Set the module to query."
+        self.opts += " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "cvsps --fast-export " + self.opts
+
+class cvs2git:
+    "Method class for cvs2git back end."
+    def __init__(self):
+        self.opts = ""
+        self.modulepath = "."
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author maping is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        sys.stderr.write("git cvsimport: cvs2git must run within a repository checkout directory.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        sys.stderr.write("git cvsimport: fuzz setting is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " --keywords-off"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " --exclude='%s'" % val
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        sys.stderr.write("git cvsimport: incremental import is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, val):
+        "Set the module to query."
+        self.modulepath = " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "(cvs2git --username=git-cvsimport --quiet --quiet --blobfile={0} --dumpfile={1} {2} {3} && cat {0} {1} && rm {0} {1})".format(tempfile.mkstemp()[1], tempfile.mkstemp()[1], self.opts, self.modulepath)
+
+class filesource:
+    "Method class for file-source back end."
+    def __init__(self, filename):
+        self.filename = filename
+    def __complain(self, legend):
+        sys.stderr.write("git cvsimport: %s with file source.\n" % legend)
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        self.__complain("repository can't be set")
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author mapping is not supported with filesource.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        self.__complain("fuzz can't be set")
+    def set_nokeywords(self, _val):
+        "Suppress CVS keyword expansion."
+        self.__complain("keyword suppression can't be set")
+    def add_opts(self, _val):
+        "Add options to the engine command line."
+        self.__complain("other options can't be set")
+    def set_exclusion(self, _val):
+        "Set a file exclusion regexp."
+        self.__complain("exclusions can't be set")
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        pass
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, _val):
+        "Set the module to query."
+        self.__complain("module can't be set")
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "cat " + self.filename
+
+if __name__ == '__main__':
+    if sys.hexversion < 0x02060000:
+        sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+        sys.exit(1)
+    (options, arguments) = getopt.getopt(sys.argv[1:], "vbe:d:C:r:o:ikus:p:z:P:S:aL:A:Rh")
+    verbose = 0
+    bare = False
+    root = None
+    outdir = os.getcwd()
+    remotize = False
+    import_only = False
+    underscore_to_dot = False
+    slashsubst = None
+    authormap = None
+    revisionmap = False
+    backend = cvsps()
+    for (opt, val) in options:
+        if opt == '-v':
+            verbose += 1
+        elif opt == '-b':
+            bare = True
+        elif opt == '-e':
+            for cls in (cvsps, cvs2git):
+                if cls.__name__ == val:
+                    backend = cls()
+                    break
+            else:
+                sys.stderr.write("git cvsimport: unknown engine %s.\n" % val)
+                sys.exit(1)
+        elif opt == '-d':
+            backend.set_repo(val)
+        elif opt == '-C':
+            outdir = val
+        elif opt == '-r':
+            remotize = True
+        elif opt == '-o':
+            sys.stderr.write("git cvsimport: -o is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-i':
+            import_only = True
+        elif opt == '-k':
+            backend.set_nokeywords()
+        elif opt == '-u':
+            underscore_to_dot = True
+        elif opt == '-s':
+            slashsubst = val
+        elif opt == '-p':
+            backend.add_opts(val.replace(",", " "))
+        elif opt == '-z':
+            backend.set_fuzz(val)
+        elif opt == '-P':
+            backend = filesource(val)
+            sys.exit(1)
+        elif opt in ('-m', '-M'):
+            sys.stderr.write("git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\n")
+            sys.exit(1)
+        elif opt == '-S':
+            backend.set_exclusion(val)
+        elif opt == '-a':
+            sys.stderr.write("git cvsimport: -a is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-L':
+            sys.stderr.write("git cvsimport: -L is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-A':
+            authormap = os.path.abspath(val)
+        elif opt == '-R':
+            revisionmap = True
+        else:
+            print """\
+git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>]
+     [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>]
+     [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>]
+     [<CVS_module>]
+"""
+    def metadata(fn, outdir='.'):
+        if bare:
+            return os.path.join(outdir, fn)
+        else:
+            return os.path.join(outdir, ".git", fn)
+    # Ugly fallback code for people with only cvsps-2.x
+    # Added January 2013 - should be removed after a decent interval.
+    if backend.__class__.__name__ == "cvsps":
+        try:
+            subprocess.check_output("cvsps -V 2>/dev/null", shell=True)
+        except subprocess.CalledProcessError as e:
+            if e.returncode == 1:
+                sys.stderr.write("cvsimport: falling back to old version...\n")
+                sys.exit(os.system("git-cvsimport-fallback " + " ".join(sys.argv[1:])))
+            else:
+                sys.stderr.write("cvsimport: cannot execute cvsps.\n")
+                sys.exit(1)
+    # Real mainline code begins here
+    try:
+        if outdir:
+            try:
+                # If the output directory does not exist, create it
+                # and initialize it as a git repository.
+                os.mkdir(outdir)
+                do_or_die("git init --quiet " + outdir)
+            except OSError:
+                # Otherwise, assume user wants incremental import.
+                if not bare and not os.path.exists(os.path.join(outdir, ".git")):
+                    raise Fatal("output directory is not a git repository")
+                threshold = capture_or_die("git log -1 --format=%ct").strip()
+                backend.set_after(threshold)
+        if revisionmap:
+            backend.set_revmap(tempfile.mkstemp()[1])
+            markmap = tempfile.mkstemp()[1]
+        if arguments:
+            backend.set_module(arguments[0])
+        gitopts = ""
+        if bare:
+            gitopts += " --bare"
+        if revisionmap:
+            gitopts += " --export-marks='%s'" % markmap
+        if authormap:
+            shutil.copyfile(authormap, metadata("cvs-authors", outdir))
+        if os.path.exists(metadata("cvs-authors", outdir)):
+            backend.set_authormap(metadata("cvs-authors", outdir))
+        do_or_die("%s | (cd %s >/dev/null; git fast-import --quiet %s)" \
+                  % (backend.command(), outdir, gitopts))
+        os.chdir(outdir)
+        if underscore_to_dot or slashsubst:
+            tagnames = capture_or_die("git tag -l")
+            for tag in tagnames.split():
+                if tag:
+                    changed = tag
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if changed != tag:
+                        do_or_die("git tag -f %s %s >/dev/null" % (tag, changed))
+        if underscore_to_dot or slashsubst or remotize:
+            branchnames = capture_or_die("git branch -l")
+            for branch in branchnames.split():
+                if branch:
+                    # Ugh - fragile dependency on branch -l output format
+                    branch = branch[2:]
+                    changed = branch
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if remotize:
+                        changed = os.path.join("remotes", remotize, branch)
+                    if changed != branch:
+                        do_or_die("branch --m %s %s >/dev/null" % (branch, changed))
+        if revisionmap:
+            refd = {}
+            for line in open(backend.revmap):
+                if line.startswith("#"):
+                    continue
+                (fn, rev, mark) = line.split()
+                refd[(fn, rev)] = mark
+            markd = {}
+            for line in open(markmap):
+                if line.startswith("#"):
+                    continue
+                (mark, hashd) = line.split()
+                markd[mark] = hashd
+            with open(metadata("cvs-revisions"), "a") as wfp:
+                for ((fn, rev), val) in refd.items():
+                    if val in markd:
+                        wfp.write("%s %s %s\n" % (fn, rev, markd[val]))
+            os.remove(markmap)
+            os.remove(backend.revmap)
+        if not import_only and not bare:
+            do_or_die("git checkout -q")
+    except Fatal, err:
+        sys.stderr.write("git_cvsimport: " + err.msg + "\n")
+        sys.exit(1)
+    except KeyboardInterrupt:
+        pass
+
+# end
diff --git a/t/t9601/cvsroot/.gitattributes b/t/t9601/cvsroot/.gitattributes
deleted file mode 100644
index 562b12e..0000000
--- a/t/t9601/cvsroot/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* -whitespace
diff --git a/t/t9601/cvsroot/CVSROOT/.gitignore b/t/t9601/cvsroot/CVSROOT/.gitignore
deleted file mode 100644
index 3bb9b34..0000000
--- a/t/t9601/cvsroot/CVSROOT/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-history
-val-tags
diff --git a/t/t9601/cvsroot/module/added-imported.txt,v b/t/t9601/cvsroot/module/added-imported.txt,v
deleted file mode 100644
index 5f83072..0000000
--- a/t/t9601/cvsroot/module/added-imported.txt,v
+++ /dev/null
@@ -1,44 +0,0 @@
-head	1.1;
-access;
-symbols
-	vtag-4:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.15;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.16;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Add a file to the working copy.
-@
-text
-@Adding this file, before importing it with different contents.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-4).
-@
-text
-@d1 1
-a1 1
-This is vtag-4 (on vbranchA) of added-then-imported.txt.
-@
-
diff --git a/t/t9601/cvsroot/module/imported-anonymously.txt,v b/t/t9601/cvsroot/module/imported-anonymously.txt,v
deleted file mode 100644
index 55e1b0c..0000000
--- a/t/t9601/cvsroot/module/imported-anonymously.txt,v
+++ /dev/null
@@ -1,42 +0,0 @@
-head	1.1;
-branch	1.1.1;
-access;
-symbols
-	vtag-1:1.1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@This is vtag-1 (on vbranchA) of imported-anonymously.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
diff --git a/t/t9601/cvsroot/module/imported-modified-imported.txt,v b/t/t9601/cvsroot/module/imported-modified-imported.txt,v
deleted file mode 100644
index e5830ae..0000000
--- a/t/t9601/cvsroot/module/imported-modified-imported.txt,v
+++ /dev/null
@@ -1,76 +0,0 @@
-head	1.2;
-access;
-symbols
-	vtag-2:1.1.1.2
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2004.02.09.15.43.14;	author kfogel;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	1.1.1.2;
-
-1.1.1.2
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@First regular commit, to imported-modified-imported.txt, on HEAD.
-@
-text
-@This is a modification of imported-modified-imported.txt on HEAD.
-It should supersede the version from the vendor branch.
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d1 2
-a2 1
-This is vtag-1 (on vbranchA) of imported-modified-imported.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
-1.1.1.2
-log
-@Import (vbranchA, vtag-2).
-@
-text
-@d1 1
-a1 1
-This is vtag-2 (on vbranchA) of imported-modified-imported.txt.
-@
-
-
diff --git a/t/t9601/cvsroot/module/imported-modified.txt,v b/t/t9601/cvsroot/module/imported-modified.txt,v
deleted file mode 100644
index bbcfe44..0000000
--- a/t/t9601/cvsroot/module/imported-modified.txt,v
+++ /dev/null
@@ -1,59 +0,0 @@
-head	1.2;
-access;
-symbols
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2004.02.09.15.43.14;	author kfogel;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Commit on HEAD.
-@
-text
-@This is a modification of imported-modified.txt on HEAD.
-It should supersede the version from the vendor branch.
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d1 2
-a2 1
-This is vtag-1 (on vbranchA) of imported-modified.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
diff --git a/t/t9601/cvsroot/module/imported-once.txt,v b/t/t9601/cvsroot/module/imported-once.txt,v
deleted file mode 100644
index c5dd82b..0000000
--- a/t/t9601/cvsroot/module/imported-once.txt,v
+++ /dev/null
@@ -1,43 +0,0 @@
-head	1.1;
-branch	1.1.1;
-access;
-symbols
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@This is vtag-1 (on vbranchA) of imported-once.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
diff --git a/t/t9601/cvsroot/module/imported-twice.txt,v b/t/t9601/cvsroot/module/imported-twice.txt,v
deleted file mode 100644
index d1f3f1b..0000000
--- a/t/t9601/cvsroot/module/imported-twice.txt,v
+++ /dev/null
@@ -1,60 +0,0 @@
-head	1.1;
-branch	1.1.1;
-access;
-symbols
-	vtag-2:1.1.1.2
-	vtag-1:1.1.1.1
-	vbranchA:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	1.1.1.2;
-
-1.1.1.2
-date	2004.02.09.15.43.13;	author kfogel;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@This is vtag-1 (on vbranchA) of imported-twice.txt.
-@
-
-
-1.1.1.1
-log
-@Import (vbranchA, vtag-1).
-@
-text
-@@
-
-
-1.1.1.2
-log
-@Import (vbranchA, vtag-2).
-@
-text
-@d1 1
-a1 1
-This is vtag-2 (on vbranchA) of imported-twice.txt.
-@
-
-
diff --git a/t/t9602/README b/t/t9602/README
deleted file mode 100644
index c231e0f..0000000
--- a/t/t9602/README
+++ /dev/null
@@ -1,62 +0,0 @@
-This repository is for testing the ability to group revisions
-correctly along tags and branches.  Here is its history:
-
-  1.  The initial import (revision 1.1 of everybody) created a
-      directory structure with a file named `default' in each dir:
-
-            ./
-              default
-              sub1/default
-                   subsubA/default
-                   subsubB/default
-              sub2/default
-                   subsubA/default
-              sub3/default
-
-  2.  Then tagged everyone with T_ALL_INITIAL_FILES.
-
-  3.  Then tagged everyone except sub1/subsubB/default with
-      T_ALL_INITIAL_FILES_BUT_ONE.
-
-  4.  Then created branch B_FROM_INITIALS on everyone.
-
-  5.  Then created branch B_FROM_INITIALS_BUT_ONE on everyone except
-      /sub1/subsubB/default.
-
-  6.  Then committed modifications to two files: sub3/default, and
-      sub1/subsubA/default.
-
-  7.  Then committed a modification to all 7 files.
-
-  8.  Then backdated sub3/default to revision 1.2, and
-      sub2/subsubA/default to revision 1.1, and tagged with T_MIXED.
-
-  9.  Same as 8, but tagged with -b to create branch B_MIXED.
-
-  10. Switched the working copy to B_MIXED, and added
-      sub2/branch_B_MIXED_only.  (That's why the RCS file is in
-      sub2/Attic/ -- it never existed on trunk.)
-
-  11. In one commit, modified default, sub1/default, and
-      sub2/subsubA/default, on branch B_MIXED.
-
-  12. Did "cvs up -A" on sub2/default, then in one commit, made a
-      change to sub2/default and sub2/branch_B_MIXED_only.  So this
-      commit should be spread between the branch and the trunk.
-
-  13. Do "cvs up -A" to get everyone back to trunk, then make a new
-      branch B_SPLIT on everyone except sub1/subsubB/default,v.
-
-  14. Switch to branch B_SPLIT (see sub1/subsubB/default disappear)
-      and commit a change that affects everyone except sub3/default.
-
-  15. An hour or so later, "cvs up -A" to get sub1/subsubB/default
-      back, then commit a change on that file, on trunk.  (It's
-      important that this change happened after the previous commits
-      on B_SPLIT.)
-
-  16. Branch sub1/subsubB/default to B_SPLIT, then "cvs up -r B_SPLIT"
-      to switch the whole working copy to the branch.
-
-  17. Commit a change on B_SPLIT, to sub1/subsubB/default and
-      sub3/default.
diff --git a/t/t9602/cvsroot/.gitattributes b/t/t9602/cvsroot/.gitattributes
deleted file mode 100644
index 562b12e..0000000
--- a/t/t9602/cvsroot/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* -whitespace
diff --git a/t/t9602/cvsroot/CVSROOT/.gitignore b/t/t9602/cvsroot/CVSROOT/.gitignore
deleted file mode 100644
index 3bb9b34..0000000
--- a/t/t9602/cvsroot/CVSROOT/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-history
-val-tags
diff --git a/t/t9602/cvsroot/module/default,v b/t/t9602/cvsroot/module/default,v
deleted file mode 100644
index 3b68382..0000000
--- a/t/t9602/cvsroot/module/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.2;
-access;
-symbols
-	B_SPLIT:1.2.0.4
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.2.2.1
-	1.2.4.1;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2003.05.23.00.31.36;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.4.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is the file `default' in the top level of the project.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.2.4.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a5 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2.2.1
-log
-@Modify three files, on branch B_MIXED.
-@
-text
-@a5 2
-
-This line was added on branch B_MIXED only (affecting 3 files).
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub1/default,v b/t/t9602/cvsroot/module/sub1/default,v
deleted file mode 100644
index b7fdccd..0000000
--- a/t/t9602/cvsroot/module/sub1/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.2;
-access;
-symbols
-	B_SPLIT:1.2.0.4
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.2.2.1
-	1.2.4.1;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2003.05.23.00.31.36;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.4.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub1/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.2.4.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a5 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2.2.1
-log
-@Modify three files, on branch B_MIXED.
-@
-text
-@a5 2
-
-This line was added on branch B_MIXED only (affecting 3 files).
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub1/subsubA/default,v b/t/t9602/cvsroot/module/sub1/subsubA/default,v
deleted file mode 100644
index 472b7b2..0000000
--- a/t/t9602/cvsroot/module/sub1/subsubA/default,v
+++ /dev/null
@@ -1,101 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.4
-	B_MIXED:1.3.0.2
-	T_MIXED:1.3
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.3.4.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.15.26;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.4.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub1/subsubA/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added by the first commit (affecting two files).
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.3.4.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a7 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2
-log
-@First commit to proj, affecting two files.
-@
-text
-@d6 2
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub1/subsubB/default,v b/t/t9602/cvsroot/module/sub1/subsubB/default,v
deleted file mode 100644
index fe6efa4..0000000
--- a/t/t9602/cvsroot/module/sub1/subsubB/default,v
+++ /dev/null
@@ -1,107 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.2
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.06.03.04.29.14;	author jrandom;	state Exp;
-branches
-	1.3.2.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.2.1
-date	2003.06.03.04.33.13;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@A trunk change to sub1/subsubB/default.  This was committed about an
-hour after an earlier change that affected most files on branch
-B_SPLIT.  This file is not on that branch yet, but after this commit,
-we'll branch to B_SPLIT, albeit rooted in a revision that didn't exist
-at the time the rest of B_SPLIT was created.
-@
-text
-@This is sub1/subsubB/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-
-This bit was committed on trunk about an hour after an earlier change
-to everyone else on branch B_SPLIT.  Afterwards, we'll finally branch
-this file to B_SPLIT, but rooted in a revision that didn't exist at
-the time the rest of B_SPLIT was created.
-@
-
-
-1.3.2.1
-log
-@This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-text
-@a10 4
-
-This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@d6 5
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v b/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v
deleted file mode 100644
index 34c9789..0000000
--- a/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v
+++ /dev/null
@@ -1,59 +0,0 @@
-head	1.1;
-access;
-symbols
-	B_MIXED:1.1.0.2;
-locks; strict;
-comment	@# @;
-
-
-1.1
-date	2003.05.23.00.25.26;	author jrandom;	state dead;
-branches
-	1.1.2.1;
-next	;
-
-1.1.2.1
-date	2003.05.23.00.25.26;	author jrandom;	state Exp;
-branches;
-next	1.1.2.2;
-
-1.1.2.2
-date	2003.05.23.00.48.51;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.1
-log
-@file branch_B_MIXED_only was initially added on branch B_MIXED.
-@
-text
-@@
-
-
-1.1.2.1
-log
-@Add a file on branch B_MIXED.
-@
-text
-@a0 1
-This file was added on branch B_MIXED.  It never existed on trunk.
-@
-
-
-1.1.2.2
-log
-@A single commit affecting one file on branch B_MIXED and one on trunk.
-@
-text
-@a1 3
-
-The same commit added these two lines here on branch B_MIXED, and two
-similar lines to ./default on trunk.
-@
-
-
diff --git a/t/t9602/cvsroot/module/sub2/default,v b/t/t9602/cvsroot/module/sub2/default,v
deleted file mode 100644
index 018f7f8..0000000
--- a/t/t9602/cvsroot/module/sub2/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.2
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.05.23.00.48.51;	author jrandom;	state Exp;
-branches
-	1.3.2.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.2.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@A single commit affecting one file on branch B_MIXED and one on trunk.
-@
-text
-@This is sub2/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-
-The same commit added these two lines here on trunk, and two similar
-lines to ./branch_B_MIXED_only on branch B_MIXED.
-@
-
-
-1.3.2.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a8 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@d6 3
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub2/subsubA/default,v b/t/t9602/cvsroot/module/sub2/subsubA/default,v
deleted file mode 100644
index d13242c..0000000
--- a/t/t9602/cvsroot/module/sub2/subsubA/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.2;
-access;
-symbols
-	B_SPLIT:1.2.0.2
-	B_MIXED:1.1.0.2
-	T_MIXED:1.1
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.2.2.1;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1
-	1.1.2.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.1.2.1
-date	2003.05.23.00.31.36;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2003.06.03.03.20.31;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub2/subsub2/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.2.2.1
-log
-@First change on branch B_SPLIT.
-
-This change excludes sub3/default, because it was not part of this
-commit, and sub1/subsubB/default, which is not even on the branch yet.
-@
-text
-@a5 2
-
-First change on branch B_SPLIT.
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.2.1
-log
-@Modify three files, on branch B_MIXED.
-@
-text
-@a3 2
-
-This line was added on branch B_MIXED only (affecting 3 files).
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9602/cvsroot/module/sub3/default,v b/t/t9602/cvsroot/module/sub3/default,v
deleted file mode 100644
index 88e4567..0000000
--- a/t/t9602/cvsroot/module/sub3/default,v
+++ /dev/null
@@ -1,102 +0,0 @@
-head	1.3;
-access;
-symbols
-	B_SPLIT:1.3.0.2
-	B_MIXED:1.2.0.2
-	T_MIXED:1.2
-	B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
-	B_FROM_INITIALS:1.1.1.1.0.2
-	T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
-	T_ALL_INITIAL_FILES:1.1.1.1
-	vendortag:1.1.1.1
-	vendorbranch:1.1.1;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2003.05.23.00.17.53;	author jrandom;	state Exp;
-branches
-	1.3.2.1;
-next	1.2;
-
-1.2
-date	2003.05.23.00.15.26;	author jrandom;	state Exp;
-branches;
-next	1.1;
-
-1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches
-	1.1.1.1;
-next	;
-
-1.1.1.1
-date	2003.05.22.23.20.19;	author jrandom;	state Exp;
-branches;
-next	;
-
-1.3.2.1
-date	2003.06.03.04.33.13;	author jrandom;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@Second commit to proj, affecting all 7 files.
-@
-text
-@This is sub3/default.
-
-Every directory in the `proj' project has a file named `default'.
-
-This line was added by the first commit (affecting two files).
-
-This line was added in the second commit (affecting all 7 files).
-@
-
-
-1.3.2.1
-log
-@This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-text
-@a7 4
-
-This change affects sub3/default and sub1/subsubB/default, on branch
-B_SPLIT.  Note that the latter file did not even exist on this branch
-until after some other files had had revisions committed on B_SPLIT.
-@
-
-
-1.2
-log
-@First commit to proj, affecting two files.
-@
-text
-@d6 2
-@
-
-
-1.1
-log
-@Initial revision
-@
-text
-@d4 2
-@
-
-
-1.1.1.1
-log
-@Initial import.
-@
-text
-@@
diff --git a/t/t9603/cvsroot/.gitattributes b/t/t9603/cvsroot/.gitattributes
deleted file mode 100644
index 562b12e..0000000
--- a/t/t9603/cvsroot/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* -whitespace
diff --git a/t/t9603/cvsroot/CVSROOT/.gitignore b/t/t9603/cvsroot/CVSROOT/.gitignore
deleted file mode 100644
index 3bb9b34..0000000
--- a/t/t9603/cvsroot/CVSROOT/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-history
-val-tags
diff --git a/t/t9603/cvsroot/module/a,v b/t/t9603/cvsroot/module/a,v
deleted file mode 100644
index ba8fd5a..0000000
--- a/t/t9603/cvsroot/module/a,v
+++ /dev/null
@@ -1,74 +0,0 @@
-head	1.2;
-access;
-symbols
-	A:1.2.0.2;
-locks; strict;
-comment	@# @;
-
-
-1.2
-date	2009.02.21.18.11.14;	author tester;	state Exp;
-branches
-	1.2.2.1;
-next	1.1;
-
-1.1
-date	2009.02.21.18.11.43;	author tester;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2009.03.11.19.03.52;	author tester;	state Exp;
-branches;
-next	1.2.2.2;
-
-1.2.2.2
-date	2009.03.11.19.09.10;	author tester;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.2
-log
-@Rev 2
-@
-text
-@1.2
-@
-
-
-1.2.2.1
-log
-@Rev 4 Branch A
-@
-text
-@d1 1
-a1 1
-1.2.2.1
-@
-
-
-1.2.2.2
-log
-@Rev 5 Branch A
-@
-text
-@d1 1
-a1 1
-1.2.2.2
-@
-
-
-1.1
-log
-@Rev 1
-@
-text
-@d1 1
-a1 1
-1.1
-@
diff --git a/t/t9603/cvsroot/module/b,v b/t/t9603/cvsroot/module/b,v
deleted file mode 100644
index d268855..0000000
--- a/t/t9603/cvsroot/module/b,v
+++ /dev/null
@@ -1,90 +0,0 @@
-head	1.3;
-access;
-symbols
-	A:1.2.0.2;
-locks; strict;
-comment	@# @;
-
-
-1.3
-date	2009.03.11.19.05.08;	author tester;	state Exp;
-branches;
-next	1.2;
-
-1.2
-date	2009.02.21.18.11.43;	author tester;	state Exp;
-branches
-	1.2.2.1;
-next	1.1;
-
-1.1
-date	2009.02.21.18.11.14;	author tester;	state Exp;
-branches;
-next	;
-
-1.2.2.1
-date	2009.03.11.19.03.52;	author tester;	state Exp;
-branches;
-next	1.2.2.2;
-
-1.2.2.2
-date	2009.03.11.19.09.10;	author tester;	state Exp;
-branches;
-next	;
-
-
-desc
-@@
-
-
-1.3
-log
-@Rev 4
-@
-text
-@1.3
-@
-
-
-1.2
-log
-@Rev 3
-@
-text
-@d1 1
-a1 1
-1.2
-@
-
-
-1.2.2.1
-log
-@Rev 4 Branch A
-@
-text
-@d1 1
-a1 1
-1.2.2.1
-@
-
-
-1.2.2.2
-log
-@Rev 5 Branch A
-@
-text
-@d1 1
-a1 1
-1.2
-@
-
-
-1.1
-log
-@Rev 2
-@
-text
-@d1 1
-a1 1
-1.1
-@
-- 
1.8.1.407.g59f98af

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-11  3:32 [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Junio C Hamano
@ 2013-01-11 16:31 ` Junio C Hamano
  2013-01-11 18:58   ` Eric S. Raymond
  2013-01-12  5:20 ` Junio C Hamano
  2013-01-12 15:13 ` Michael Haggerty
  2 siblings, 1 reply; 37+ messages in thread
From: Junio C Hamano @ 2013-01-11 16:31 UTC (permalink / raw)
  To: Eric S. Raymond; +Cc: git

> From: "Eric S. Raymond" <esr@thyrsus.com>
> ...
> diff --git a/git-cvsimport.perl b/git-cvsimport-fallback.perl
> similarity index 98%
> rename from git-cvsimport.perl
> rename to git-cvsimport-fallback.perl
> index 0a31ebd..4bc0717 100755
> --- a/git-cvsimport.perl
> +++ b/git-cvsimport-fallback.perl
> @@ -1,4 +1,8 @@
>  #!/usr/bin/perl
> +# This code became obsolete in January 2013, and is retained only as a
> +# fallback from git-cvsimport.py for users who have only cvsps-2.x.
> +# It (and the code in cvsimport.py that calls it) should be removed
> +# once the 3.x version has had a reasonable time to propagate.
>  
>  # This tool is copyright (c) 2005, Matthias Urlichs.
>  # It is released under the Gnu Public License, version 2.
> @@ -27,6 +31,10 @@
>  use POSIX qw(strftime tzset dup2 ENOENT);
>  use IPC::Open2;
>  
> +print(STDERR "You do not appear to have cvsps 3.x.\n");
> +print(STDERR "Falling back to unmaintained Perl cvsimport for cvsps 2.x.\n");
> +print(STDERR "Upgrade your cvsps for best results.\n");

I think the prevalent style in this script is to write "print"
without parentheses:

	print STDERR "msg\n";

> diff --git a/git-cvsimport.py b/git-cvsimport.py
> new file mode 100755
> index 0000000..129471e
> --- /dev/null
> +++ b/git-cvsimport.py
> @@ -0,0 +1,354 @@
> +#!/usr/bin/env python
> +#
> +# Import CVS history into git
> +#
> +# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.
> ...
> +class cvsps:
> +    "Method class for cvsps back end."
> +    def __init__(self):
> +        self.opts = ""
> +        self.revmap = None
> +    def set_repo(self, val):
> +        "Set the repository root option."
> +        if not val.startswith(":"):
> +            if not val.startswith(os.sep):
> +                val = os.path.abspath(val)
> +            val = ":local:" + val
> +        self.opts += " --root '%s'" % val

This looks lazy and unsafe quoting.  Is there anything that makes
sure repository path does not contain a single quote?

> +    def set_authormap(self, val):
> +        "Set the author-map file."
> +        self.opts += " -A '%s'" % val
> +    def set_fuzz(self, val):
> +        "Set the commit-similarity window."
> +        self.opts += " -z %s" % val
> +    def set_nokeywords(self):
> +        "Suppress CVS keyword expansion."
> +        self.opts += " -k"
> +    def add_opts(self, val):
> +        "Add options to the engine command line."
> +        self.opts += " " + val

... especially for callers of this method.

The same comment applies to many uses of "val" in the method
implementations of this class and the cvs2git class.

> +    def command(self):
> +        "Emit the command implied by all previous options."
> +        return "(cvs2git --username=git-cvsimport --quiet --quiet --blobfile={0} --dumpfile={1} {2} {3} && cat {0} {1} && rm {0} {1})".format(tempfile.mkstemp()[1], tempfile.mkstemp()[1], self.opts, self.modulepath)

Could we do something better with this overlong source line?

> +if __name__ == '__main__':
> ...
> +    for (opt, val) in options:
> +        if opt == '-v':
> +            verbose += 1
> +        elif opt == '-b':
> +            bare = True
> +        elif opt == '-e':
> +            for cls in (cvsps, cvs2git):
> +                if cls.__name__ == val:
> +                    backend = cls()
> +                    break
> +            else:
> +                sys.stderr.write("git cvsimport: unknown engine %s.\n" % val)
> +                sys.exit(1)
> +        elif opt == '-d':
> +            backend.set_repo(val)
> +        elif opt == '-C':
> +            outdir = val
> +        elif opt == '-r':
> +            remotize = True
> +        elif opt == '-o':
> +            sys.stderr.write("git cvsimport: -o is no longer supported.\n")
> +            sys.exit(1)

Isn't this a regression?

> +        elif opt == '-i':
> +            import_only = True
> +        elif opt == '-k':
> +            backend.set_nokeywords()
> +        elif opt == '-u':
> +            underscore_to_dot = True
> +        elif opt == '-s':
> +            slashsubst = val
> +        elif opt == '-p':
> +            backend.add_opts(val.replace(",", " "))
> +        elif opt == '-z':
> ...
> +        elif opt == '-P':
> +            backend = filesource(val)
> +            sys.exit(1)

???

> +        elif opt in ('-m', '-M'):
> +            sys.stderr.write("git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\n")
> +            sys.exit(1)

I wonder if it is better to ignore these options with a warning but
still let the command continue; cvsps-3.x was supposed to get merges
right without the help of these ad-hoc options, no?

Otherwise it looks like a regression to me.

> +        elif opt == '-S':
> +            backend.set_exclusion(val)
> +        elif opt == '-a':
> +            sys.stderr.write("git cvsimport: -a is no longer supported.\n")
> +            sys.exit(1)
> +        elif opt == '-L':
> +            sys.stderr.write("git cvsimport: -L is no longer supported.\n")
> +            sys.exit(1)
> +        elif opt == '-A':
> +            authormap = os.path.abspath(val)
> +        elif opt == '-R':
> +            revisionmap = True
> +        else:
> +            print """\
> +git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>]
> +     [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>]
> +     [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>]
> +     [<CVS_module>]
> +"""
> +    def metadata(fn, outdir='.'):
> +        if bare:
> +            return os.path.join(outdir, fn)
> +        else:
> +            return os.path.join(outdir, ".git", fn)
> +    # Ugly fallback code for people with only cvsps-2.x
> +    # Added January 2013 - should be removed after a decent interval.
> +    if backend.__class__.__name__ == "cvsps":
> +        try:
> +            subprocess.check_output("cvsps -V 2>/dev/null", shell=True)
> +        except subprocess.CalledProcessError as e:
> +            if e.returncode == 1:
> +                sys.stderr.write("cvsimport: falling back to old version...\n")
> +                sys.exit(os.system("git-cvsimport-fallback " + " ".join(sys.argv[1:])))
> +            else:
> +                sys.stderr.write("cvsimport: cannot execute cvsps.\n")
> +                sys.exit(1)

Having the code to die when it sees options the rewritten version
does not yet support before it calls the fallback makes the fallback
much less effective, no?

> +    # Real mainline code begins here
> +    try:
> +        if outdir:
> +            try:
> +                # If the output directory does not exist, create it
> +                # and initialize it as a git repository.
> +                os.mkdir(outdir)
> +                do_or_die("git init --quiet " + outdir)

Did anything made sure outdir is without $IFS chars up to this
point?

Not very impressed (yet).  The advertised "fix major bugs" sounds
more like "trade major bugs with different ones with a couple of
feature removals" at this point.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-11 16:31 ` Junio C Hamano
@ 2013-01-11 18:58   ` Eric S. Raymond
  2013-01-11 19:17     ` Junio C Hamano
  2013-01-11 19:27     ` Junio C Hamano
  0 siblings, 2 replies; 37+ messages in thread
From: Eric S. Raymond @ 2013-01-11 18:58 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano <gitster@pobox.com>:
> I think the prevalent style in this script is to write "print"
> without parentheses:
> 
> 	print STDERR "msg\n";

That can be easily fixed.

> This looks lazy and unsafe quoting.  Is there anything that makes
> sure repository path does not contain a single quote?

No. But...wait, checking...the Perl code didn't have the analogous
check, so there's no increased vulnerability here.  I'll put it on the
to-do list for after I ship parsecvs.

> > +    def command(self):
> > +        "Emit the command implied by all previous options."
> > +        return "(cvs2git --username=git-cvsimport --quiet --quiet --blobfile={0} --dumpfile={1} {2} {3} && cat {0} {1} && rm {0} {1})".format(tempfile.mkstemp()[1], tempfile.mkstemp()[1], self.opts, self.modulepath)
> 
> Could we do something better with this overlong source line?

Yes.  The correct fix is to simplify cvs2git's rather baroque command-line 
interface.  Michael Haggerty has accepted that patch.  Soon that line will
look like this:

     return "cvs2git --quiet --quiet {0} {1}".format(self.opts, self.modulepath)

Older versions of cvs2git will terminate cleanly with an error message 
when called this way.

> > +        elif opt == '-o':
> > +            sys.stderr.write("git cvsimport: -o is no longer supported.\n")
> > +            sys.exit(1)
> 
> Isn't this a regression?

It would be if the -o behavior were consistent and decently
documented.  When I tested this option with the Perl version I got no
result.  Possibly my usage was incorrect; if anyone can be found who
actually understands how it's supposed to work in detail and will tell
me, I can probably support it.

> > +        elif opt in ('-m', '-M'):
> > +            sys.stderr.write("git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\n")
> > +            sys.exit(1)
> 
> I wonder if it is better to ignore these options with a warning but
> still let the command continue; cvsps-3.x was supposed to get merges
> right without the help of these ad-hoc options, no?

Sorry, I don't know where you got that idea. I don't think the general merge
detection that would need is possible even in principle.

> Otherwise it looks like a regression to me.

There are two reasons -m and -M aren't supported.

One is implementation-level.  The wrapper script no longer deals with
individual files or changesets or branches; it relies on the
conversion engine to do all that.  (As it should - the old design was
a mess with lots of stuff being done at the wrong level.)  But the
conversion engines don't implement -m or -M, and aren't ever going to
(see next paragraph).

The other is a design-level problem - these options were a bad idea to
begin with.  In earlier list mail I said

    An example of the batchiness mistake close to home is the -m and -M
    options in the old version of cvsimport.  It takes human judgment
    looking at the whole commit DAG in gitspace to decide what merge
    points would best express the (as you say, sometimes ambiguous) CVS
    history - what's needed is a scalpel and sutures in a surgeon's hand,
    not a regexp hammer.

One specific problem with the regexp hammer is false-positive matches
leading to unintended merges.

That's why I won't implement these in cvsps or parsecvs. Instead I've
just added DAG visualization via graphviz in reposurgeon, so a human
can quickly see candidate merges in the visualization and do them by
hand.  This is better and safer.

> Having the code to die when it sees options the rewritten version
> does not yet support before it calls the fallback makes the fallback
> much less effective, no?

Only to the extent that -o/-m/-M are really important, which I doubt.
But that might be fixable, and I'll put it on the to-do list.

> Not very impressed (yet).  The advertised "fix major bugs" sounds
> more like "trade major bugs with different ones with a couple of
> feature removals" at this point.

If you think that, you have failed to understand just how broken and
dangerous the old combination is.  None of the details you've called
out are "major" by any stretch of the imagination compared to it
silently botching the translation of repositories.

Also bear in mind that leaving the old Perl code in place is not going
to be a viable option for more than a few months out.  As cvsps-3.x
propagates to the distros what you have is going to stop even its
current pretense of working.

Finally...my own purposes are fulfilled by having CVS exporters that can
do a decent job of front-ending for reposurgeon. Rewriting git's
wrapper script was extra work that I took on only because I wanted to
be friendly to the git project, *but*... 

...there is a limit to the amount of what I consider pointless
hoop-jumping that friendliness will buy you, and the 2.x fallback eas
already pushing that limit.  Tread a little more gently, Junio; I've
put in a lot of hard, boring work on git-cvsimport over the last two
weeks when I would rather have been doing other things, and my
patience for being nit-picked without appreciation or reward has a
correspondingly low limit.  We'll both be happier if you don't reach
it.
-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-11 18:58   ` Eric S. Raymond
@ 2013-01-11 19:17     ` Junio C Hamano
  2013-01-11 19:27     ` Junio C Hamano
  1 sibling, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-11 19:17 UTC (permalink / raw)
  To: esr; +Cc: git

"Eric S. Raymond" <esr@thyrsus.com> writes:

> Junio C Hamano <gitster@pobox.com>:
>> I think the prevalent style in this script is to write "print"
>> without parentheses:
>> 
>> 	print STDERR "msg\n";
>
> That can be easily fixed.
>
>> This looks lazy and unsafe quoting.  Is there anything that makes
>> sure repository path does not contain a single quote?
>
> No. But...wait, checking...the Perl code didn't have the analogous
> check, so there's no increased vulnerability here.  I'll put it on the
> to-do list for after I ship parsecvs.

I checked before I sent that review, and as far as I could tell, it
was fairly consistently avoiding the lazy and insecure forms, e.g.

	system("com mand " . $param);
	open($fh, "com mand " . $param . " |"); while (<$fh>)	{ ... }

but used the more sequre list form, e.g.

	system(qw(com mand), $param);
        open($fh, "-|", qw(com mand), $param); while (<$fh>)	{ ... }

But of course there may be some places that were careless that I
didn't spot (and previous reviewers of the current cvsimport
didn't).

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-11 18:58   ` Eric S. Raymond
  2013-01-11 19:17     ` Junio C Hamano
@ 2013-01-11 19:27     ` Junio C Hamano
  2013-01-12  5:04       ` Eric S. Raymond
  1 sibling, 1 reply; 37+ messages in thread
From: Junio C Hamano @ 2013-01-11 19:27 UTC (permalink / raw)
  To: esr; +Cc: git

"Eric S. Raymond" <esr@thyrsus.com> writes:

> Junio C Hamano <gitster@pobox.com>:
> ...
> The other is a design-level problem - these options were a bad idea to
> begin with.  In earlier list mail I said
>
>     An example of the batchiness mistake close to home is the -m and -M
>     options in the old version of cvsimport.  It takes human judgment
>     looking at the whole commit DAG in gitspace to decide what merge
>     points would best express the (as you say, sometimes ambiguous) CVS
>     history - what's needed is a scalpel and sutures in a surgeon's hand,
>     not a regexp hammer.
>
> One specific problem with the regexp hammer is false-positive matches
> leading to unintended merges.

Yeah, it is OK to _discourage_ its use, but to me it looks like that
the above is a fairly subjective policy decision, not something I
should let you impose on the users of the old cvsimport, which you
do not seem to even treat as your users.

>> Having the code to die when it sees options the rewritten version
>> does not yet support before it calls the fallback makes the fallback
>> much less effective, no?
>
> Only to the extent that -o/-m/-M are really important, which I doubt.
> But that might be fixable, and I'll put it on the to-do list.
>
>> Not very impressed (yet).  The advertised "fix major bugs" sounds
>> more like "trade major bugs with different ones with a couple of
>> feature removals" at this point.
>
> If you think that, you have failed to understand just how broken and
> dangerous the old combination is.  None of the details you've called
> out are "major" by any stretch of the imagination compared to it
> silently botching the translation of repositories.

The "major" in my sentence was from your description (the bugs you
fixed), and not about the new ones you still have in this draft.  I
did not mean to say that you are trading fixes to "major" bugs with
different "major" bugs.

Insecure quoting of parameters is much easier to fix; it does need
to be addressed, though.

It is just that looking at the state of the patch as submitted left
me with a feeling that this topic needs a lot more time to mature
than I previously was led to believe by your earlier messages, which
made me someaht sad.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-11 19:27     ` Junio C Hamano
@ 2013-01-12  5:04       ` Eric S. Raymond
  0 siblings, 0 replies; 37+ messages in thread
From: Eric S. Raymond @ 2013-01-12  5:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano <gitster@pobox.com>:
> Yeah, it is OK to _discourage_ its use, but to me it looks like that
> the above is a fairly subjective policy decision, not something I
> should let you impose on the users of the old cvsimport, which you
> do not seem to even treat as your users.

Er.  You still don't seem to grasp the fundamentals of this
situation. I'm not imposing any damn thing on the users.  What's
imposing is the fact that cvsps-2.x and the Perl cvsimport are both
individually and collectively *broken right now*, and within a few
months the Perl git-cvsimport is going to cease even pretending to
work.  I'm trying to *fix that problem* as best I can, fixing it
required two radical rewrites, and criticizing me for not emulating
every last detail and misfeature immediately is every bit as pointless
and annoying as arguing about the fabric on the deck chairs while the
ship is sinking.

To put it bluntly, you should be grateful to be getting back any
functionality at all - because the alternative is that the Perl
git-cvsimport will hang out in your tree as a dead piece of cruft.
Your choice is between making it easy for me replace it with minimum
disruption now and hoping for someone else to replace it months from
now after you've had a bunch of unhappy users bitching at you.

So let me be more direct.  I think the -M and -m options are
sufficiently bad ideas that I am *not willing* to put in the quite
large amount of effort that would be required to implement them in cvsps
or parsecvs.  That would be a bad use of my time.

This is not the case with -o; that might be a good idea if I
understood it. This is also not like the 2.x fallback; I thought that
was a bad idea (because it would be better for users that the
combination break in an obvious way than continue breaking in a silent
one), but it was a small enough effort that I was willing to do it
anyway to keep the git maintainer happy. The effort to fix the quoting
bugs is even easier for me to justify; they are actual bugs.

Those are my engineering judgments; go ahead and call them
"subjective" if you like, but neither the facts nor my judgment will
change on that account.

> The "major" in my sentence was from your description (the bugs you
> fixed), and not about the new ones you still have in this draft.  I
> did not mean to say that you are trading fixes to "major" bugs with
> different "major" bugs.

OK, thank you.  In the future I will try to bear in mind that English
is not your primary language when I evaluate statements that seems a bit
offensive.

So what's your next bid? Note that you can't increase my friction and
hassle costs much more before I give up and let you deal with the
consequences without me. I want to do the right thing, but I have
more other projects clamoring for my attention than you could easily
guess.  I need to get git-cvsimport *finished* and off my hands -
I may already have given it more time than I really should have.

So give me your minimum list of deliverables before you'll merge,
please, and then stick to it.  I assume fixes for the quoting bugs
will be on that list.
-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-11  3:32 [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Junio C Hamano
  2013-01-11 16:31 ` Junio C Hamano
@ 2013-01-12  5:20 ` Junio C Hamano
  2013-01-12  5:38   ` [PATCH] t/t960[123]: remove leftover scripts Junio C Hamano
                     ` (2 more replies)
  2013-01-12 15:13 ` Michael Haggerty
  2 siblings, 3 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-12  5:20 UTC (permalink / raw)
  To: Eric S. Raymond; +Cc: git

I cloned git://gitorious.org/cvsps/cvsps.git and installed cvsps-3.7
at c2ce6cc (More fun with test loads, sigh.  Timezones suck.,
2013-01-09) earlier on my $PATH, and tried to run t96xx series with
this patch applied on top of Git 1.8.1.

The first thing I noticed was that all the tests were skipped.
A patch to t/lib-cvs.sh might be sufficient, 

--------------------- >8 -------------------------
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index 44263ad..423953f 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -17,6 +17,8 @@ cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
 case "$cvsps_version" in
 2.1 | 2.2*)
 	;;
+3.*)
+	;;
 '')
 	skip_all='skipping cvsimport tests, cvsps not found'
 	test_done
--------------------- 8< -------------------------

but I didn't check more than "now it seems not to skip them".
And here is what I got:

--------------------- >8 -------------------------
Test Summary Report
-------------------
t9600-cvsimport.sh              (Wstat: 256 Tests: 15 Failed: 9)
  Failed tests:  4-6, 8-9, 11-13, 15
  Non-zero exit status: 1
t9601-cvsimport-vendor-branch.sh (Wstat: 256 Tests: 9 Failed: 8)
  Failed tests:  1-4, 6-9
  Non-zero exit status: 1
t9602-cvsimport-branches-tags.sh (Wstat: 256 Tests: 11 Failed: 5)
  Failed tests:  1-3, 7, 9
  Non-zero exit status: 1
t9604-cvsimport-timestamps.sh   (Wstat: 256 Tests: 2 Failed: 2)
  Failed tests:  1-2
  Non-zero exit status: 1
Files=5, Tests=38,  5 wallclock secs ( 0.05 usr  0.01 sys +  0.49
  cusr  0.16 csys =  0.71 CPU)
Result: FAIL
--------------------- 8< -------------------------

A funny thing was that without cvsps-3.7 on $PATH (which means I am
getting distro packaged cvsps 2.1), I got identical errors.  Looking
at the log message, it seems that you meant to remove t960[123], so
perhaps the patch simply forgot to remove 9601 and 9602?

As neither test runs "git cvsimport" with -o/-m/-M options, ideally
we should be able to pass them with and without having cvsps-3.x.
Not passing them without cvsps-3.x would mean that the fallback mode
of rewritten cvsimport is not working as expected. Not passing them
with cvsps-3.x may mean the tests were expecting a wrong conversion
result, or they uncover bugs in the replacement cvsimport.

t9600 fails with "-a is no longer supported", even without having
cvsps-3.x on the $PATH (i.e. attempting to use the fallback).  I
wonder if this is an option the updated cvsimport would want to
simply ignore?

It is a way to tell the old cvsps/cvsimport to disable its
heuristics to ignore commits made within the last 10 minutes (this
is done in the hope of waiting for the per-file nature of CVS
commits to stabilize, IIUC); the user tells the command that he
knows that the CVS repository is now quiescent and it is safe to
import the whole thing.

If the updated cvsps can identify the changeset more reliably and it
no longer needs "-a" option, it may be more helpful to the users to
migrate their script if it allowed, warned and then ignored the
option.  It certainly would help sharing of this test script between
runs that use the old and new cvsps as backends.

t9601 (after resurrecting the t/t9601/cvsroot directory) fails in an
interesting way.

--------------------- >8 -------------------------
$ sh t9601-cvsimport-vendor-branch.sh -i -v
Initialized empty Git repository in /git/git.build/t/trash directory.t9601-cvsimport-vendor-branch/.git/
expecting success:

        git cvsimport -C module-git module

Traceback (most recent call last):
  File "/git/git.build/git-cvsimport", line 262, in <module>
    subprocess.check_output("cvsps -V 2>/dev/null", shell=True)
AttributeError: 'module' object has no attribute 'check_output'
not ok - 1 import a module with a vendor branch
--------------------- 8< -------------------------

Apparently, the copy of "subprocess.py" I have does not give us the
check_output thing:

--------------------- >8 -------------------------
$ python
Python 2.6.6 (r266:84292, Dec 26 2010, 22:31:48)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> dir(subprocess)
['CalledProcessError', 'MAXFD', 'PIPE', 'Popen', 'STDOUT', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_active', '_cleanup', '_demo_posix', '_demo_windows', '_eintr_retry_call', 'call', 'check_call', 'errno', 'fcntl', 'gc', 'list2cmdline', 'mswindows', 'os', 'pickle', 'select', 'signal', 'sys', 'traceback', 'types']
--------------------- 8< -------------------------

The story is the same for t9602 and t9603 (again after resurrecting
the necessary files).

http://docs.python.org/2/library/subprocess.html tells me that
check_output has first become available in 2.7.

So... does this mean that we now set the minimum required version of
Python to 2.7?  I dunno.

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH] t/t960[123]: remove leftover scripts
  2013-01-12  5:20 ` Junio C Hamano
@ 2013-01-12  5:38   ` Junio C Hamano
  2013-01-12  6:06     ` Chris Rorvick
  2013-01-12  8:40   ` [PATCH] t/lib-cvs: cvsimport no longer works without Python >= 2.7 Junio C Hamano
  2013-01-12 15:47   ` [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Eric S. Raymond
  2 siblings, 1 reply; 37+ messages in thread
From: Junio C Hamano @ 2013-01-12  5:38 UTC (permalink / raw)
  To: Eric S. Raymond; +Cc: git, Chris Rorvick

The rewrite patch was supposed to remove these scripts, but somehow
we ended up removing only the supporting files for them but not the
test script themselves.  Remove them for real.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * I'll queue this on top of your patch together with a few fix-up
   patches from Chris Rorvick.  This may have been caused by your
   private patch e-mail mangled somewhere between us before I resent
   your patch, or perhaps you simply may have forgot to remove them,
   but at this point I do not really care where these deletions were
   lost---the only thing I care about is to make sure that you
   _meant_ to remove them in your patch (i.e. if you didn't mean to,
   then I am further breaking the tests in a way you did not intend
   to), so I'd appreciate either "Yup, these three files should be
   removed", or "No, they should stay; removal of their supporting
   data is no longer needed" from you (I and this patch expect the
   former, of course).

   By the way, Chris, we'll need your Sign-off on the three paches
   (t/lib-cvs.sh fix to allow cvsps v3, t9600 fix and t9604 fix).

 t/t9601-cvsimport-vendor-branch.sh | 85 --------------------------------------
 t/t9602-cvsimport-branches-tags.sh | 78 ----------------------------------
 t/t9603-cvsimport-patchsets.sh     | 39 -----------------
 3 files changed, 202 deletions(-)
 delete mode 100755 t/t9601-cvsimport-vendor-branch.sh
 delete mode 100755 t/t9602-cvsimport-branches-tags.sh
 delete mode 100755 t/t9603-cvsimport-patchsets.sh

diff --git a/t/t9601-cvsimport-vendor-branch.sh b/t/t9601-cvsimport-vendor-branch.sh
deleted file mode 100755
index 827d39f..0000000
--- a/t/t9601-cvsimport-vendor-branch.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/sh
-
-# Description of the files in the repository:
-#
-#    imported-once.txt:
-#
-#       Imported once.  1.1 and 1.1.1.1 should be identical.
-#
-#    imported-twice.txt:
-#
-#       Imported twice.  HEAD should reflect the contents of the
-#       second import (i.e., have the same contents as 1.1.1.2).
-#
-#    imported-modified.txt:
-#
-#       Imported, then modified on HEAD.  HEAD should reflect the
-#       modification.
-#
-#    imported-modified-imported.txt:
-#
-#       Imported, then modified on HEAD, then imported again.
-#
-#    added-imported.txt,v:
-#
-#       Added with 'cvs add' to create 1.1, then imported with
-#       completely different contents to create 1.1.1.1, therefore the
-#       vendor branch was never the default branch.
-#
-#    imported-anonymously.txt:
-#
-#       Like imported-twice.txt, but with a vendor branch whose branch
-#       tag has been removed.
-
-test_description='git cvsimport handling of vendor branches'
-. ./lib-cvs.sh
-
-setup_cvs_test_repository t9601
-
-test_expect_success PERL 'import a module with a vendor branch' '
-
-	git cvsimport -C module-git module
-
-'
-
-test_expect_success PERL 'check HEAD out of cvs repository' 'test_cvs_co master'
-
-test_expect_success PERL 'check master out of git repository' 'test_git_co master'
-
-test_expect_success PERL 'check a file that was imported once' '
-
-	test_cmp_branch_file master imported-once.txt
-
-'
-
-test_expect_failure PERL 'check a file that was imported twice' '
-
-	test_cmp_branch_file master imported-twice.txt
-
-'
-
-test_expect_success PERL 'check a file that was imported then modified on HEAD' '
-
-	test_cmp_branch_file master imported-modified.txt
-
-'
-
-test_expect_success PERL 'check a file that was imported, modified, then imported again' '
-
-	test_cmp_branch_file master imported-modified-imported.txt
-
-'
-
-test_expect_success PERL 'check a file that was added to HEAD then imported' '
-
-	test_cmp_branch_file master added-imported.txt
-
-'
-
-test_expect_success PERL 'a vendor branch whose tag has been removed' '
-
-	test_cmp_branch_file master imported-anonymously.txt
-
-'
-
-test_done
diff --git a/t/t9602-cvsimport-branches-tags.sh b/t/t9602-cvsimport-branches-tags.sh
deleted file mode 100755
index e1db323..0000000
--- a/t/t9602-cvsimport-branches-tags.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-
-# A description of the repository used for this test can be found in
-# t9602/README.
-
-test_description='git cvsimport handling of branches and tags'
-. ./lib-cvs.sh
-
-setup_cvs_test_repository t9602
-
-test_expect_success PERL 'import module' '
-
-	git cvsimport -C module-git module
-
-'
-
-test_expect_success PERL 'test branch master' '
-
-	test_cmp_branch_tree master
-
-'
-
-test_expect_success PERL 'test branch vendorbranch' '
-
-	test_cmp_branch_tree vendorbranch
-
-'
-
-test_expect_failure PERL 'test branch B_FROM_INITIALS' '
-
-	test_cmp_branch_tree B_FROM_INITIALS
-
-'
-
-test_expect_failure PERL 'test branch B_FROM_INITIALS_BUT_ONE' '
-
-	test_cmp_branch_tree B_FROM_INITIALS_BUT_ONE
-
-'
-
-test_expect_failure PERL 'test branch B_MIXED' '
-
-	test_cmp_branch_tree B_MIXED
-
-'
-
-test_expect_success PERL 'test branch B_SPLIT' '
-
-	test_cmp_branch_tree B_SPLIT
-
-'
-
-test_expect_failure PERL 'test tag vendortag' '
-
-	test_cmp_branch_tree vendortag
-
-'
-
-test_expect_success PERL 'test tag T_ALL_INITIAL_FILES' '
-
-	test_cmp_branch_tree T_ALL_INITIAL_FILES
-
-'
-
-test_expect_failure PERL 'test tag T_ALL_INITIAL_FILES_BUT_ONE' '
-
-	test_cmp_branch_tree T_ALL_INITIAL_FILES_BUT_ONE
-
-'
-
-test_expect_failure PERL 'test tag T_MIXED' '
-
-	test_cmp_branch_tree T_MIXED
-
-'
-
-
-test_done
diff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh
deleted file mode 100755
index 52034c8..0000000
--- a/t/t9603-cvsimport-patchsets.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/sh
-
-# Structure of the test cvs repository
-#
-# Message   File:Content         Commit Time
-# Rev 1     a: 1.1               2009-02-21 19:11:43 +0100
-# Rev 2     a: 1.2    b: 1.1     2009-02-21 19:11:14 +0100
-# Rev 3               b: 1.2     2009-02-21 19:11:43 +0100
-#
-# As you can see the commit of Rev 3 has the same time as
-# Rev 1 this leads to a broken import because of a cvsps
-# bug.
-
-test_description='git cvsimport testing for correct patchset estimation'
-. ./lib-cvs.sh
-
-setup_cvs_test_repository t9603
-
-test_expect_failure 'import with criss cross times on revisions' '
-
-    git cvsimport -p"-x" -C module-git module &&
-    (cd module-git &&
-        git log --pretty=format:%s > ../actual-master &&
-        git log A~2..A --pretty="format:%s %ad" -- > ../actual-A &&
-        echo "" >> ../actual-master &&
-	echo "" >> ../actual-A
-    ) &&
-    echo "Rev 4
-Rev 3
-Rev 2
-Rev 1" > expect-master &&
-    test_cmp actual-master expect-master &&
-
-    echo "Rev 5 Branch A Wed Mar 11 19:09:10 2009 +0000
-Rev 4 Branch A Wed Mar 11 19:03:52 2009 +0000" > expect-A &&
-    test_cmp actual-A expect-A
-'
-
-test_done
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* Re: [PATCH] t/t960[123]: remove leftover scripts
  2013-01-12  5:38   ` [PATCH] t/t960[123]: remove leftover scripts Junio C Hamano
@ 2013-01-12  6:06     ` Chris Rorvick
  0 siblings, 0 replies; 37+ messages in thread
From: Chris Rorvick @ 2013-01-12  6:06 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Eric S. Raymond, git

On Fri, Jan 11, 2013 at 11:38 PM, Junio C Hamano <gitster@pobox.com> wrote:
>    By the way, Chris, we'll need your Sign-off on the three paches
>    (t/lib-cvs.sh fix to allow cvsps v3, t9600 fix and t9604 fix).

Sure.  I was just maintaining them for myself but thought I'd share
when I saw the follow-up patch.  Didn't think to amend them.

Chris

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH] t/lib-cvs: cvsimport no longer works without Python >= 2.7
  2013-01-12  5:20 ` Junio C Hamano
  2013-01-12  5:38   ` [PATCH] t/t960[123]: remove leftover scripts Junio C Hamano
@ 2013-01-12  8:40   ` Junio C Hamano
  2013-01-12 15:27     ` Michael Haggerty
  2013-01-12 15:47   ` [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Eric S. Raymond
  2 siblings, 1 reply; 37+ messages in thread
From: Junio C Hamano @ 2013-01-12  8:40 UTC (permalink / raw)
  To: Eric S. Raymond; +Cc: git

The new cvsimport requires at least Python 2.7 to work; do not fail
the cvsimport tests on platforms without one.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 Junio C Hamano <gitster@pobox.com> writes:

 > http://docs.python.org/2/library/subprocess.html tells me that
 > check_output has first become available in 2.7.
 >
 > So... does this mean that we now set the minimum required version of
 > Python to 2.7?  I dunno.

 Even if we were to rip out the fallback code that uses the 2.7-only
 subprocess.check_output() on "cvsps -V", the function is also used
 for doing the real work interacting with cvsps-3.x, so I think this
 patch will be necessary.  Unless new cvsimport is tweaked not to
 use the method, that is.

 A suggestion for a better alternative is of course very much
 appreciated.

 I do not want to keep pushing integration results that do not pass
 tests even for myself for too many integration cycles, so I'll keep
 this patch at the tip of the topic, at least for now.

 t/lib-cvs.sh | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index b55e861..4e890ea 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -27,6 +27,21 @@ case "$cvsps_version" in
 	;;
 esac
 
+if ! test_have_prereq PYTHON
+then
+	skipall='skipping cvsimport tests, no python'
+	test_done
+fi
+
+python -c '
+import sys
+if sys.hexversion < 0x02070000:
+	sys.exit(1)
+' || {
+	skip_all='skipping cvsimport tests, python too old (< 2.7)'
+	test_done
+}
+
 setup_cvs_test_repository () {
 	CVSROOT="$(pwd)/.cvsroot" &&
 	cp -r "$TEST_DIRECTORY/$1/cvsroot" "$CVSROOT" &&
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-11  3:32 [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Junio C Hamano
  2013-01-11 16:31 ` Junio C Hamano
  2013-01-12  5:20 ` Junio C Hamano
@ 2013-01-12 15:13 ` Michael Haggerty
  2013-01-12 16:11   ` Eric S. Raymond
  2013-01-12 18:26   ` Jonathan Nieder
  2 siblings, 2 replies; 37+ messages in thread
From: Michael Haggerty @ 2013-01-12 15:13 UTC (permalink / raw)
  To: Eric S. Raymond; +Cc: Junio C Hamano, git, Chris Rorvick

On 01/11/2013 04:32 AM, Junio C Hamano wrote:
> From: "Eric S. Raymond" <esr@thyrsus.com>
> 
> The combination of git-cvsimport and cvsps had serious problems.

Agreed.

> [...]
> This patch also removes Michael Haggerty's git-cvsimport tests
> (t960[123]) from the git tree.  These are actually conversion-engine
> tests and have been merged into a larger cvsps test suite, which I
> intend to spin out into a general CVS-lifting test that can also be
> applied to utilities such as cvs2git and parsecvs.  The t9604 test
> will move in a future patch, when I likewise have it integrated
> into the general test suite.
> 
> The following known bug has not been fixed: "If any files were ever
> "cvs import"ed more than once (e.g., import of more than one vendor
> release) the HEAD contains the wrong content." However, cvsps now
> emits a warning in this case. There is also one pathological tagging
> case that was successful in the former t9602 test that now fails
> (with a warning).
> 
> I plan to address these problems. This patch at least gets the
> cvsps-3.x/git-cvsimport combination to a state that is not too
> broken to ship - that is, in all failure cases known to me it
> now emits useful warnings rather than silently botching the
> import.

I don't understand the logic of removing the cvsimport tests, at least
not at this time.  It is true that the tests mostly ensure that the
conversion engine is working correctly, especially with your new version
of cvsps.  But I think the git project, by implicitly endorsing the use
of cvsps, has some responsibility to verify that the combination cvsps +
git-cvsimport continues to work and to document any known breakages via
its test suite.

Otherwise, how do we know that cvsps currently works with git-cvsimport?
 (OK, you claim that it does, but in the next breath you admit that
there is a new failure in "one pathological tagging case".)  How can we
understand its strengths/weaknesses?  How can we gain confidence that it
works on different platforms?  How will we find out if a future versions
of cvsps stops working (e.g., because of a breakage or a
non-backwards-compatible change)?

Normally one would expect an improvement like this to be combined with
patches that turn test expected failures into expected successes, not to
rip out the very tests that establish the correctness of the change that
is being proposed!


Let me describe what I would consider to be the optimum state of the
test suite.  Maybe your idea of "optimum" differs from mine, or maybe
the optimum is unrealistic due to lack of resources or for some other
reason.  But if so, let's explicitly spell out why we are deviating from
whatever optimum we define.

* The old tests should be retained (and possibly new tests added to show
off your improvements).

* There should be a way for users to choose which cvsps executable to
use when running test suite.  (In the future, the selection might be
expanded to cover altogether different conversion engines.)

* The tests should determine which version of cvsps has been selected
(e.g., by running "cvsps --version").

* The individual tests should be marked expected success/expected
failure based on the selected version of cvsps; in other words, some
tests might be marked "expected failure" if cvsps 2.x is being used but
"expected success" if cvsps 3.x is being used.


Regarding your claim that "within a few months the Perl git-cvsimport is
going to cease even pretending to work": It might be that the old
git-cvsimport will stop working *for people who upgrade to cvsps 3.x*.
But it is not realistic to expect people to synchronize their git and
cvsps version upgrades.  It is even quite possible that this or that
Linux distribution will package incompatible versions of the two packages.

Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] t/lib-cvs: cvsimport no longer works without Python >= 2.7
  2013-01-12  8:40   ` [PATCH] t/lib-cvs: cvsimport no longer works without Python >= 2.7 Junio C Hamano
@ 2013-01-12 15:27     ` Michael Haggerty
  2013-01-13 17:17       ` John Keeping
  0 siblings, 1 reply; 37+ messages in thread
From: Michael Haggerty @ 2013-01-12 15:27 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Eric S. Raymond, git

I have the feeling I'm only seeing one side of this conversation...

On 01/12/2013 09:40 AM, Junio C Hamano wrote:
> The new cvsimport requires at least Python 2.7 to work; do not fail
> the cvsimport tests on platforms without one.
> 
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
> 
>  Junio C Hamano <gitster@pobox.com> writes:
> 
>  > http://docs.python.org/2/library/subprocess.html tells me that
>  > check_output has first become available in 2.7.
>  >
>  > So... does this mean that we now set the minimum required version of
>  > Python to 2.7?  I dunno.

It would be unfortunate to set the minimum Python version to 2.7 if "git
cvsimport" is considered an integral part of git.

>  Even if we were to rip out the fallback code that uses the 2.7-only
>  subprocess.check_output() on "cvsps -V", the function is also used
>  for doing the real work interacting with cvsps-3.x, so I think this
>  patch will be necessary.  Unless new cvsimport is tweaked not to
>  use the method, that is.
> 
>  A suggestion for a better alternative is of course very much
>  appreciated.

If the only reason to require Python 2.7 is subprocess.check_output(),
it would be easy to reimplement it (it is only 12 lines of
straightforward code, plus a few lines to define the exception type
CalledProcessError).  According to [1], the Python license is
GPL-compatible; therefore these lines could even be copied into
git-cvsimport.

Michael

[1] http://www.gnu.org/licenses/license-list.html#Python

-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-12  5:20 ` Junio C Hamano
  2013-01-12  5:38   ` [PATCH] t/t960[123]: remove leftover scripts Junio C Hamano
  2013-01-12  8:40   ` [PATCH] t/lib-cvs: cvsimport no longer works without Python >= 2.7 Junio C Hamano
@ 2013-01-12 15:47   ` Eric S. Raymond
  2 siblings, 0 replies; 37+ messages in thread
From: Eric S. Raymond @ 2013-01-12 15:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano <gitster@pobox.com>:
> And here is what I got:

Hm. In my version of these tests, I only have one regression from the
old combo (in the pathological tags test, t9602).  You're seeing more
breakage than that, obviously.

> A funny thing was that without cvsps-3.7 on $PATH (which means I am
> getting distro packaged cvsps 2.1), I got identical errors.

That suggests that something in your test setup has gone bad and is
introducing spurious errors. Which would be consistent with the above.

>                                                     Looking
> at the log message, it seems that you meant to remove t960[123], so
> perhaps the patch simply forgot to remove 9601 and 9602?

Yes.
 
> As neither test runs "git cvsimport" with -o/-m/-M options, ideally
> we should be able to pass them with and without having cvsps-3.x.
> Not passing them without cvsps-3.x would mean that the fallback mode
> of rewritten cvsimport is not working as expected. Not passing them
> with cvsps-3.x may mean the tests were expecting a wrong conversion
> result, or they uncover bugs in the replacement cvsimport.

That's possible, but seems unlikely.  Because the new cvsimport is
such a thin wrapper around the conversion engine, bugs in it should
lead to obvious crashes or failure to run the engine rather than the 
sort of conversion error the t960* tests are designed to check.  Really
all it does is assemble options to pass to the conversion engines.

My test strategy is aimed at the engine, not the wrapper. I took the
repos from t960*  and wrote a small Python framework to check the same 
assertions as the git-tree tests do, but using the engine.  For example,
here's how my t9602 looks:

import os, cvspstest

cc = cvspstest.ConvertComparison("t9602", "module")
cc.cmp_branch_tree("test of branch", "master", True)
cc.cmp_branch_tree("test of branch", "vendorbranch", True)
cc.cmp_branch_tree("test of branch", "B_FROM_INITIALS", False)
cc.cmp_branch_tree("test of branch", "B_FROM_INITIALS_BUT_ONE", False)
cc.cmp_branch_tree("test of branch", "B_MIXED", False)
cc.cmp_branch_tree("test of branch", "B_SPLIT", True)
cc.cmp_branch_tree("test of tag", "vendortag", False)
# This is the only test new cvsps fails that old git-cvsimport passed.
cc.cmp_branch_tree("test of tag", "T_ALL_INITIAL_FILES", True)
cc.cmp_branch_tree("test of tag", "T_ALL_INITIAL_FILES_BUT_ONE", False)
cc.cmp_branch_tree("test of tag", "T_MIXED", False)
cc.cleanup()
 
> t9600 fails with "-a is no longer supported", even without having
> cvsps-3.x on the $PATH (i.e. attempting to use the fallback).  I
> wonder if this is an option the updated cvsimport would want to
> simply ignore?

Probably.  But I don't think you should keep these tests in the git tree.
That wasn't a great idea even when you were supporting just one engine;
with two (and soon three) it's going to be just silly.  Let sanity-checking
the engines be *my* problem, since I have to do it anyway.

(I'm working towards the generalized test suite as fast as I can.  First
results probably in four days or so.)

> It is a way to tell the old cvsps/cvsimport to disable its
> heuristics to ignore commits made within the last 10 minutes (this
> is done in the hope of waiting for the per-file nature of CVS
> commits to stabilize, IIUC); the user tells the command that he
> knows that the CVS repository is now quiescent and it is safe to
> import the whole thing.

Yes, that's just what -a is supposed to do.  But is should be
irrelevant for testing - in the test framework CVS is running locally, 
so there's no network lag.

> So... does this mean that we now set the minimum required version of
> Python to 2.7?  I dunno.

That would be bad, IMO.  I'll put backporting to 2.6 high on my to-do list.
-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-12 15:13 ` Michael Haggerty
@ 2013-01-12 16:11   ` Eric S. Raymond
  2013-01-12 18:16     ` Jonathan Nieder
  2013-01-12 18:26   ` Jonathan Nieder
  1 sibling, 1 reply; 37+ messages in thread
From: Eric S. Raymond @ 2013-01-12 16:11 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Junio C Hamano, git, Chris Rorvick

Michael Haggerty <mhagger@alum.mit.edu>:
> Otherwise, how do we know that cvsps currently works with git-cvsimport?
> (OK, you claim that it does, but in the next breath you admit that
> there is a new failure in "one pathological tagging case".)  How can we
> understand its strengths/weaknesses?  How can we gain confidence that it
> works on different platforms?  How will we find out if a future versions
> of cvsps stops working (e.g., because of a breakage or a
> non-backwards-compatible change)?

You can't.  But in practice the git crew was going to lose that
capability anyway simply because the new wrapper will support three
engines rather than just one.  It's not practical for the git tests to
handle that many variant external dependencies.

However, there is a solution.

The solution is for git to test that the wrapper is *generating the
expected commands*.  So what the git tree ends up with is conditional
assurance; the wrapper will do the right thing if the engine it calls
is working correctly.  I think that's really all the git-tree tests
can hope for.

Michael, the engines are my problem and yours - it's *our*
responsibility to develop a (hopefully shared) test suite to verify
that they convert repos correctly.  I'm working my end as fast as I can;
I hope to have the test suite factored out of cvsps and ready to check 
multiple engines by around Wednesday.  I still need to convert t9604,
too.

I have parsecvs working since yesterday, so we really are up to three
engines.

I have two minor features I need to merge into parsecvs before 
I can start on splitting out the test suite.
-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-12 16:11   ` Eric S. Raymond
@ 2013-01-12 18:16     ` Jonathan Nieder
  0 siblings, 0 replies; 37+ messages in thread
From: Jonathan Nieder @ 2013-01-12 18:16 UTC (permalink / raw)
  To: Eric S. Raymond; +Cc: Michael Haggerty, Junio C Hamano, git, Chris Rorvick

Hi Eric,

Eric S. Raymond wrote:

>             But in practice the git crew was going to lose that
> capability anyway simply because the new wrapper will support three
> engines rather than just one.  It's not practical for the git tests to
> handle that many variant external dependencies.

See the git-blame/git-annotate tests for an example of how the
testsuite handles "variations on a theme".

It works fine.

Hope that helps,
Jonathan

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-12 15:13 ` Michael Haggerty
  2013-01-12 16:11   ` Eric S. Raymond
@ 2013-01-12 18:26   ` Jonathan Nieder
  2013-01-13 22:20     ` Junio C Hamano
  1 sibling, 1 reply; 37+ messages in thread
From: Jonathan Nieder @ 2013-01-12 18:26 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Eric S. Raymond, Junio C Hamano, git, Chris Rorvick

Michael Haggerty wrote:

> Regarding your claim that "within a few months the Perl git-cvsimport is
> going to cease even pretending to work": It might be that the old
> git-cvsimport will stop working *for people who upgrade to cvsps 3.x*.
> But it is not realistic to expect people to synchronize their git and
> cvsps version upgrades.  It is even quite possible that this or that
> Linux distribution will package incompatible versions of the two packages.

Moreover, I feel an obligation to point the following out:

In a hypothetical world where cvsps 3.x simply breaks "git cvsimport"
it is likely that some distributions would just stick to the existing
cvsps and not upgrade to 3.x.  Maybe that's a wrong choice, but that's
a choice some would make.  An even more likely outcome in that
hypothetical world is that they would ship it renamed to something
like "cvsps3" alongside the existing cvsps.  Or they could rename the
old version to "cvsps2".  If we were the last holdout, we could even
bundle it as compat/cvsps.

So please do not act as though the cvsps upgrade is a crisis that we
need to break ourselves for at threat of no longer working at all.
The threat doesn't hold water.

Luckily you have already written patches to make "git cvsimport" work
with cvsps 3.x, and through your work you are making a better
argument: "The new cvsimport + cvsps will work better, at least for
some users, than the old tool."

Just don't pretend you have the power to force a change for a less
sensible reason than that!

Hope that helps,
Jonathan

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] t/lib-cvs: cvsimport no longer works without Python >= 2.7
  2013-01-12 15:27     ` Michael Haggerty
@ 2013-01-13 17:17       ` John Keeping
  0 siblings, 0 replies; 37+ messages in thread
From: John Keeping @ 2013-01-13 17:17 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Junio C Hamano, Eric S. Raymond, git

On Sat, Jan 12, 2013 at 04:27:36PM +0100, Michael Haggerty wrote:
>>  Even if we were to rip out the fallback code that uses the 2.7-only
>>  subprocess.check_output() on "cvsps -V", the function is also used
>>  for doing the real work interacting with cvsps-3.x, so I think this
>>  patch will be necessary.  Unless new cvsimport is tweaked not to
>>  use the method, that is.
>> 
>>  A suggestion for a better alternative is of course very much
>>  appreciated.
> 
> If the only reason to require Python 2.7 is subprocess.check_output(),
> it would be easy to reimplement it (it is only 12 lines of
> straightforward code, plus a few lines to define the exception type
> CalledProcessError).  According to [1], the Python license is
> GPL-compatible; therefore these lines could even be copied into
> git-cvsimport.

Note that this has already be done in git_remote_helpers.util.  Is there
any reason not to just reference that?


John

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-12 18:26   ` Jonathan Nieder
@ 2013-01-13 22:20     ` Junio C Hamano
  2013-01-13 23:27       ` Junio C Hamano
                         ` (3 more replies)
  0 siblings, 4 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-13 22:20 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Michael Haggerty, Eric S. Raymond, git, Chris Rorvick

Jonathan Nieder <jrnieder@gmail.com> writes:

> Michael Haggerty wrote:
>
>> Regarding your claim that "within a few months the Perl git-cvsimport is
>> going to cease even pretending to work": It might be that the old
>> git-cvsimport will stop working *for people who upgrade to cvsps 3.x*.
>> But it is not realistic to expect people to synchronize their git and
>> cvsps version upgrades.  It is even quite possible that this or that
>> Linux distribution will package incompatible versions of the two packages.
>
> Moreover, I feel an obligation to point the following out:
>
> In a hypothetical world where cvsps 3.x simply breaks "git cvsimport"
> it is likely that some distributions would just stick to the existing
> cvsps and not upgrade to 3.x.  Maybe that's a wrong choice, but that's
> a choice some would make.  An even more likely outcome in that
> hypothetical world is that they would ship it renamed to something
> like "cvsps3" alongside the existing cvsps.  Or they could rename the
> old version to "cvsps2".  If we were the last holdout, we could even
> bundle it as compat/cvsps.
>
> So please do not act as though the cvsps upgrade is a crisis that we
> need to break ourselves for at threat of no longer working at all.
> The threat doesn't hold water.
>
> Luckily you have already written patches to make "git cvsimport" work
> with cvsps 3.x, and through your work you are making a better
> argument: "The new cvsimport + cvsps will work better, at least for
> some users, than the old tool."
>
> Just don't pretend you have the power to force a change for a less
> sensible reason than that!

After a quick survey of various distros, I think it is very unlikely
that we will see "distros move on to newer cvsps, leaving cvsimport
broken" situation. If anything, it is more like "distros decide to
ignore the new cvsps, until it is made to work with cvsimport" [*1*].

I think it is probably sensible to rename the current cvsimport to
cvsimport-2, write a small wrapper git-cvsimport.sh which is
something like this:

----- >8 -----
#!/bin/sh

if test -z "$GIT_CVSPS_VERSION"
then
	case "$(cvsps -h 2>&1 | grep 'cvsps version')" in
        2.*)
		GIT_CVSPS_VERSION=2
                ;;
	3.*)
		GIT_CVSPS_VERSION=3
                ;;
	esac
fi

if test -z "$GIT_CVSPS_VERSION" 
then
	echo >&2 "No supported cvsps available"
	exit 1
fi

exec git cvsimport-$GIT_CVSPS_VERSION "$@"
----- 8< -----

and put Eric's one as git-cvsimport-3 (after ripping out the code to
fallback to the old cvsimport).  The longer term trend will be to
move away from cvsimport-2, as it is unlikely cvsps-2.x will gain
improvements, if any; keeping fallback code outside cvsimport-3 will
be a better first step in the healthier long term code evolution.

We will keep the current t96xx series of tests, and have them export
GIT_CVSPS_VERSION=2 at the beginning, protect them with test prereq
that requires presence of cvsps 2.x; this will still make sure that
the current cvsimport users will not see any regressions.

Eric's one should be polished enough to produce good results on the
simpler sample CVS histories t96xx deal with soonish if not right
now, so we can use a method similar to how we shared tests between
blame and annotate while both were _different_ implementations to
make sure the newer blame did not inroduce regression by running the
same set of tests.  Where the result _ought_ to differ, we should
also add tests that work only with the new cvsimport, of course.

I could help getting the ball rolling on this, if everybody agrees
that the above is a sensible direction to go, given the real world
constraints of distro inertia.

Agreed?


[References]

*1* Fedora, Debian and Ubuntu do not even have cvsps 3.x in their
bleeding edges, OpenBSD and NetBSD do not seem to have it either,
and Gentoo and ArchLinux have the cvsps 3.x blocked due to
incompatiblity.

http://pkgs.fedoraproject.org/cgit/cvsps.git/
http://packages.debian.org/search?keywords=cvsps
http://packages.ubuntu.com/search?keywords=cvsps

http://packages.gentoo.org/package/dev-vcs/cvsps
https://bugs.gentoo.org/show_bug.cgi?id=450424

https://bugs.archlinux.org/task/33363?project=1&cat%5B0%5D=2&string=cvsps

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-13 22:20     ` Junio C Hamano
@ 2013-01-13 23:27       ` Junio C Hamano
  2013-01-14  1:40       ` [PATCH 0/3] A smoother transition plan for cvsimport Junio C Hamano
                         ` (2 subsequent siblings)
  3 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-13 23:27 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Michael Haggerty, Eric S. Raymond, git, Chris Rorvick

Junio C Hamano <gitster@pobox.com> writes:

> Jonathan Nieder <jrnieder@gmail.com> writes:
>
>> Michael Haggerty wrote:
>>
>>> Regarding your claim that "within a few months the Perl git-cvsimport is
>>> going to cease even pretending to work": It might be that the old
>>> git-cvsimport will stop working *for people who upgrade to cvsps 3.x*.
>>> But it is not realistic to expect people to synchronize their git and
>>> cvsps version upgrades.  It is even quite possible that this or that
>>> Linux distribution will package incompatible versions of the two packages.
>>
>> Moreover, I feel an obligation to point the following out:
>>
>> In a hypothetical world where cvsps 3.x simply breaks "git cvsimport"
>> it is likely that some distributions would just stick to the existing
>> cvsps and not upgrade to 3.x.  Maybe that's a wrong choice, but that's
>> a choice some would make.  An even more likely outcome in that
>> hypothetical world is that they would ship it renamed to something
>> like "cvsps3" alongside the existing cvsps.  Or they could rename the
>> old version to "cvsps2".  If we were the last holdout, we could even
>> bundle it as compat/cvsps.
>>
>> So please do not act as though the cvsps upgrade is a crisis that we
>> need to break ourselves for at threat of no longer working at all.
>> The threat doesn't hold water.
>>
>> Luckily you have already written patches to make "git cvsimport" work
>> with cvsps 3.x, and through your work you are making a better
>> argument: "The new cvsimport + cvsps will work better, at least for
>> some users, than the old tool."
>>
>> Just don't pretend you have the power to force a change for a less
>> sensible reason than that!
>
> After a quick survey of various distros, I think it is very unlikely
> that we will see "distros move on to newer cvsps, leaving cvsimport
> broken" situation. If anything, it is more like "distros decide to
> ignore the new cvsps, until it is made to work with cvsimport" [*1*].
>
> I think it is probably sensible to rename the current cvsimport to
> cvsimport-2, write a small wrapper git-cvsimport.sh which is
> something like this:
>
> ----- >8 -----
> #!/bin/sh
>
> if test -z "$GIT_CVSPS_VERSION"
> then
> 	case "$(cvsps -h 2>&1 | grep 'cvsps version')" in
>         2.*)
> 		GIT_CVSPS_VERSION=2
>                 ;;
> 	3.*)
> 		GIT_CVSPS_VERSION=3
>                 ;;
> 	esac
> fi
>
> if test -z "$GIT_CVSPS_VERSION" 
> then
> 	echo >&2 "No supported cvsps available"
> 	exit 1
> fi
>
> exec git cvsimport-$GIT_CVSPS_VERSION "$@"
> ----- 8< -----
>
> and put Eric's one as git-cvsimport-3 (after ripping out the code to
> fallback to the old cvsimport).  The longer term trend will be to
> move away from cvsimport-2, as it is unlikely cvsps-2.x will gain
> improvements, if any; keeping fallback code outside cvsimport-3 will
> be a better first step in the healthier long term code evolution.
>
> We will keep the current t96xx series of tests, and have them export
> GIT_CVSPS_VERSION=2 at the beginning, protect them with test prereq
> that requires presence of cvsps 2.x; this will still make sure that
> the current cvsimport users will not see any regressions.
>
> Eric's one should be polished enough to produce good results on the

s/should be polished enough/should be in a polished enough state/
that is.  Also "if not right now" may better convey what I meant if
written "if not already".

> simpler sample CVS histories t96xx deal with soonish if not right
> now, so we can use a method similar to how we shared tests between
> blame and annotate while both were _different_ implementations to
> make sure the newer blame did not inroduce regression by running the
> same set of tests.  Where the result _ought_ to differ, we should
> also add tests that work only with the new cvsimport, of course.
>
> I could help getting the ball rolling on this, if everybody agrees
> that the above is a sensible direction to go, given the real world
> constraints of distro inertia.
>
> Agreed?
>
>
> [References]
>
> *1* Fedora, Debian and Ubuntu do not even have cvsps 3.x in their
> bleeding edges, OpenBSD and NetBSD do not seem to have it either,
> and Gentoo and ArchLinux have the cvsps 3.x blocked due to
> incompatiblity.
>
> http://pkgs.fedoraproject.org/cgit/cvsps.git/
> http://packages.debian.org/search?keywords=cvsps
> http://packages.ubuntu.com/search?keywords=cvsps
>
> http://packages.gentoo.org/package/dev-vcs/cvsps
> https://bugs.gentoo.org/show_bug.cgi?id=450424
>
> https://bugs.archlinux.org/task/33363?project=1&cat%5B0%5D=2&string=cvsps

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 0/3] A smoother transition plan for cvsimport
  2013-01-13 22:20     ` Junio C Hamano
  2013-01-13 23:27       ` Junio C Hamano
@ 2013-01-14  1:40       ` Junio C Hamano
  2013-01-14  1:40         ` [PATCH 1/3] cvsimport: allow setting a custom cvsps (2.x) program name Junio C Hamano
                           ` (2 more replies)
  2013-01-14  5:12       ` [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Michael Haggerty
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
  3 siblings, 3 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  1:40 UTC (permalink / raw)
  To: git; +Cc: jrnieder, mhagger, esr, chris

So here is a start of how such a transition plan outlined in the
previous message may look like.

The first two are preparatory step to allow the current code to
still work even when cvsps2 and cvsps3 are both available on the
system.

The last patch is mostly for illustration purposes; the cvsimport-3.py
script it adds was taken from the patch Eric sent earlier and I relayed
to the list, and does not have later improvements in Eric's tree or
any of Chris's patches.

Junio C Hamano (3):
  cvsimport: allow setting a custom cvsps (2.x) program name
  cvsimport: introduce a version-switch wrapper
  cvsimport: start adding cvsps 3.x support

 .gitignore           |    1 +
 Makefile             |   28 +-
 git-cvsimport-2.perl | 1179 ++++++++++++++++++++++++++++++++++++++++++++++++++
 git-cvsimport-3.py   |  344 +++++++++++++++
 git-cvsimport.perl   | 1177 -------------------------------------------------
 git-cvsimport.sh     |    5 +
 t/lib-cvs.sh         |    4 +-
 7 files changed, 1553 insertions(+), 1185 deletions(-)
 create mode 100755 git-cvsimport-2.perl
 create mode 100755 git-cvsimport-3.py
 delete mode 100755 git-cvsimport.perl
 create mode 100755 git-cvsimport.sh

-- 
1.8.1.421.g6236851

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH 1/3] cvsimport: allow setting a custom cvsps (2.x) program name
  2013-01-14  1:40       ` [PATCH 0/3] A smoother transition plan for cvsimport Junio C Hamano
@ 2013-01-14  1:40         ` Junio C Hamano
  2013-01-14  1:40         ` [PATCH 2/3] cvsimport: introduce a version-switch wrapper Junio C Hamano
  2013-01-14  1:40         ` [PATCH 3/3] cvsimport: start adding cvsps 3.x support Junio C Hamano
  2 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  1:40 UTC (permalink / raw)
  To: git; +Cc: jrnieder, mhagger, esr, chris

Distros may ship old cvsps under a different name, or the user may
install it outside the normal $PATH.  Allow setting CVSPS2_PATH from
the build environment.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile           | 9 +++++++--
 git-cvsimport.perl | 4 +++-
 t/lib-cvs.sh       | 4 +++-
 3 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/Makefile b/Makefile
index 1b30d7b..8cb4a1b 100644
--- a/Makefile
+++ b/Makefile
@@ -571,9 +571,11 @@ endif
 ifndef PYTHON_PATH
 	PYTHON_PATH = /usr/bin/python
 endif
+ifndef CVSPS2_PATH
+	CVSPS2_PATH = cvsps
+endif
 
-export PERL_PATH
-export PYTHON_PATH
+export PERL_PATH PYTHON_PATH CVSPS2_PATH
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
@@ -1511,6 +1513,7 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+CVSPS2_PATH_SQ = $(subst ','\'',$(CVSPS2_PATH))
 DIFF_SQ = $(subst ','\'',$(DIFF))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1724,6 +1727,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl GIT-VERSION-FILE
 	    -e '	H' \
 	    -e '	x' \
 	    -e '}' \
+	    -e 's|@@CVSPS2_PATH@@|$(CVSPS2_PATH_SQ)|g' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
 	    $@.perl >$@+ && \
 	chmod +x $@+ && \
@@ -2102,6 +2106,7 @@ GIT-LDFLAGS: FORCE
 GIT-BUILD-OPTIONS: FORCE
 	@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
 	@echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
+	@echo CVSPS2_PATH=\''$(subst ','\'',$(CVSPS2_PATH_SQ))'\' >>$@
 	@echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@
 	@echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
 	@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 0a31ebd..ad460a5 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -33,6 +33,8 @@
 our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R);
 my (%conv_author_name, %conv_author_email, %conv_author_tz);
 
+my $cvsps2 = "@@CVSPS2_PATH@@";
+
 sub usage(;$) {
 	my $msg = shift;
 	print(STDERR "Error: $msg\n") if $msg;
@@ -751,7 +753,7 @@ sub munge_user_filename {
 		unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
 			push @opt, '--cvs-direct';
 		}
-		exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+		exec($cvsps2,"--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
 		die "Could not start cvsps: $!\n";
 	}
 	($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index 44263ad..bdab63c 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -13,7 +13,9 @@ fi
 CVS="cvs -f"
 export CVS
 
-cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
+CVSPS="$CVSPS2_PATH"
+
+cvsps_version=`$CVSPS -h 2>&1 | sed -ne 's/cvsps version //p'`
 case "$cvsps_version" in
 2.1 | 2.2*)
 	;;
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH 2/3] cvsimport: introduce a version-switch wrapper
  2013-01-14  1:40       ` [PATCH 0/3] A smoother transition plan for cvsimport Junio C Hamano
  2013-01-14  1:40         ` [PATCH 1/3] cvsimport: allow setting a custom cvsps (2.x) program name Junio C Hamano
@ 2013-01-14  1:40         ` Junio C Hamano
  2013-01-14  1:47           ` Junio C Hamano
  2013-01-14  1:40         ` [PATCH 3/3] cvsimport: start adding cvsps 3.x support Junio C Hamano
  2 siblings, 1 reply; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  1:40 UTC (permalink / raw)
  To: git; +Cc: jrnieder, mhagger, esr, chris

In preparation to have both old and new cvsimport during the
transition period, rename the cvsimport script to cvsimport-2
and introduce a small wrapper that we can later change it to
allow users to run either frontend (with their corresponding
cvsps backends).

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 .gitignore           |    1 +
 Makefile             |    3 +-
 git-cvsimport-2.perl | 1179 ++++++++++++++++++++++++++++++++++++++++++++++++++
 git-cvsimport.perl   | 1179 --------------------------------------------------
 git-cvsimport.sh     |    5 +
 5 files changed, 1187 insertions(+), 1180 deletions(-)
 create mode 100755 git-cvsimport-2.perl
 delete mode 100755 git-cvsimport.perl
 create mode 100755 git-cvsimport.sh

diff --git a/.gitignore b/.gitignore
index aa258a6..8cb799c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@
 /git-credential-store
 /git-cvsexportcommit
 /git-cvsimport
+/git-cvsimport-2
 /git-cvsserver
 /git-daemon
 /git-diff
diff --git a/Makefile b/Makefile
index 8cb4a1b..b022db2 100644
--- a/Makefile
+++ b/Makefile
@@ -435,6 +435,7 @@ unexport CDPATH
 
 SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
+SCRIPT_SH += git-cvsimport.sh
 SCRIPT_SH += git-difftool--helper.sh
 SCRIPT_SH += git-filter-branch.sh
 SCRIPT_SH += git-lost-found.sh
@@ -463,7 +464,7 @@ SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
-SCRIPT_PERL += git-cvsimport.perl
+SCRIPT_PERL += git-cvsimport-2.perl
 SCRIPT_PERL += git-cvsserver.perl
 SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
diff --git a/git-cvsimport-2.perl b/git-cvsimport-2.perl
new file mode 100755
index 0000000..ad460a5
--- /dev/null
+++ b/git-cvsimport-2.perl
@@ -0,0 +1,1179 @@
+#!/usr/bin/perl
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to aggregate CVS check-ins into related changes.
+# Fortunately, "cvsps" does that for us; all we have to do is to parse
+# its output.
+#
+# Checking out the files is done by a single long-running CVS connection
+# / server process.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+use 5.008;
+use strict;
+use warnings;
+use Getopt::Long;
+use File::Spec;
+use File::Temp qw(tempfile tmpnam);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Socket;
+use IO::Pipe;
+use POSIX qw(strftime tzset dup2 ENOENT);
+use IPC::Open2;
+
+$SIG{'PIPE'}="IGNORE";
+set_timezone('UTC');
+
+our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R);
+my (%conv_author_name, %conv_author_email, %conv_author_tz);
+
+my $cvsps2 = "@@CVSPS2_PATH@@";
+
+sub usage(;$) {
+	my $msg = shift;
+	print(STDERR "Error: $msg\n") if $msg;
+	print STDERR <<END;
+Usage: git cvsimport     # fetch/update GIT from CVS
+       [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
+       [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
+       [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
+       [-r remote] [-R] [CVS_module]
+END
+	exit(1);
+}
+
+sub read_author_info($) {
+	my ($file) = @_;
+	my $user;
+	open my $f, '<', "$file" or die("Failed to open $file: $!\n");
+
+	while (<$f>) {
+		# Expected format is this:
+		#   exon=Andreas Ericsson <ae@op5.se>
+		if (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/) {
+			$user = $1;
+			$conv_author_name{$user} = $2;
+			$conv_author_email{$user} = $3;
+		}
+		# or with an optional timezone:
+		#   spawn=Simon Pawn <spawn@frog-pond.org> America/Chicago
+		elsif (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*(\S+?)\s*$/) {
+			$user = $1;
+			$conv_author_name{$user} = $2;
+			$conv_author_email{$user} = $3;
+			$conv_author_tz{$user} = $4;
+		}
+		# However, we also read from CVSROOT/users format
+		# to ease migration.
+		elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) {
+			my $mapped;
+			($user, $mapped) = ($1, $3);
+			if ($mapped =~ /^\s*(.*?)\s*<(.*)>\s*$/) {
+				$conv_author_name{$user} = $1;
+				$conv_author_email{$user} = $2;
+			}
+			elsif ($mapped =~ /^<?(.*)>?$/) {
+				$conv_author_name{$user} = $user;
+				$conv_author_email{$user} = $1;
+			}
+		}
+		# NEEDSWORK: Maybe warn on unrecognized lines?
+	}
+	close ($f);
+}
+
+sub write_author_info($) {
+	my ($file) = @_;
+	open my $f, '>', $file or
+	  die("Failed to open $file for writing: $!");
+
+	foreach (keys %conv_author_name) {
+		print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>";
+		print $f " $conv_author_tz{$_}" if ($conv_author_tz{$_});
+		print $f "\n";
+	}
+	close ($f);
+}
+
+# Versions of perl before 5.10.0 may not automatically check $TZ each
+# time localtime is run (most platforms will do so only the first time).
+# We can work around this by using tzset() to update the internal
+# variable whenever we change the environment.
+sub set_timezone {
+	$ENV{TZ} = shift;
+	tzset();
+}
+
+# convert getopts specs for use by git config
+my %longmap = (
+	'A:' => 'authors-file',
+	'M:' => 'merge-regex',
+	'P:' => undef,
+	'R' => 'track-revisions',
+	'S:' => 'ignore-paths',
+);
+
+sub read_repo_config {
+	# Split the string between characters, unless there is a ':'
+	# So "abc:de" becomes ["a", "b", "c:", "d", "e"]
+	my @opts = split(/ *(?!:)/, shift);
+	foreach my $o (@opts) {
+		my $key = $o;
+		$key =~ s/://g;
+		my $arg = 'git config';
+		$arg .= ' --bool' if ($o !~ /:$/);
+		my $ckey = $key;
+
+		if (exists $longmap{$o}) {
+			# An uppercase option like -R cannot be
+			# expressed in the configuration, as the
+			# variable names are downcased.
+			$ckey = $longmap{$o};
+			next if (! defined $ckey);
+			$ckey =~ s/-//g;
+		}
+		chomp(my $tmp = `$arg --get cvsimport.$ckey`);
+		if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
+			no strict 'refs';
+			my $opt_name = "opt_" . $key;
+			if (!$$opt_name) {
+				$$opt_name = $tmp;
+			}
+		}
+	}
+}
+
+my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R";
+read_repo_config($opts);
+Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
+
+# turn the Getopt::Std specification in a Getopt::Long one,
+# with support for multiple -M options
+GetOptions( map { s/:/=s/; /M/ ? "$_\@" : $_ } split( /(?!:)/, $opts ) )
+    or usage();
+usage if $opt_h;
+
+if (@ARGV == 0) {
+		chomp(my $module = `git config --get cvsimport.module`);
+		push(@ARGV, $module) if $? == 0;
+}
+@ARGV <= 1 or usage("You can't specify more than one CVS module");
+
+if ($opt_d) {
+	$ENV{"CVSROOT"} = $opt_d;
+} elsif (-f 'CVS/Root') {
+	open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
+	$opt_d = <$f>;
+	chomp $opt_d;
+	close $f;
+	$ENV{"CVSROOT"} = $opt_d;
+} elsif ($ENV{"CVSROOT"}) {
+	$opt_d = $ENV{"CVSROOT"};
+} else {
+	usage("CVSROOT needs to be set");
+}
+$opt_s ||= "-";
+$opt_a ||= 0;
+
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $remote;
+if (defined $opt_r) {
+	$remote = 'refs/remotes/' . $opt_r;
+	$opt_o ||= "master";
+} else {
+	$opt_o ||= "origin";
+	$remote = 'refs/heads';
+}
+
+my $cvs_tree;
+if ($#ARGV == 0) {
+	$cvs_tree = $ARGV[0];
+} elsif (-f 'CVS/Repository') {
+	open my $f, '<', 'CVS/Repository' or
+	    die 'Failed to open CVS/Repository';
+	$cvs_tree = <$f>;
+	chomp $cvs_tree;
+	close $f;
+} else {
+	usage("CVS module has to be specified");
+}
+
+our @mergerx = ();
+if ($opt_m) {
+	@mergerx = ( qr/\b(?:from|of|merge|merging|merged) ([-\w]+)/i );
+}
+if (@opt_M) {
+	push (@mergerx, map { qr/$_/ } @opt_M);
+}
+
+# Remember UTC of our starting time
+# we'll want to avoid importing commits
+# that are too recent
+our $starttime = time();
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package CVSconn;
+# Basic CVS dialog.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+
+sub new {
+	my ($what,$repo,$subdir) = @_;
+	$what=ref($what) if ref($what);
+
+	my $self = {};
+	$self->{'buffer'} = "";
+	bless($self,$what);
+
+	$repo =~ s#/+$##;
+	$self->{'fullrep'} = $repo;
+	$self->conn();
+
+	$self->{'subdir'} = $subdir;
+	$self->{'lines'} = undef;
+
+	return $self;
+}
+
+sub find_password_entry {
+	my ($cvspass, @cvsroot) = @_;
+	my ($file, $delim) = @$cvspass;
+	my $pass;
+	local ($_);
+
+	if (open(my $fh, $file)) {
+		# :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
+		CVSPASSFILE:
+		while (<$fh>) {
+			chomp;
+			s/^\/\d+\s+//;
+			my ($w, $p) = split($delim,$_,2);
+			for my $cvsroot (@cvsroot) {
+				if ($w eq $cvsroot) {
+					$pass = $p;
+					last CVSPASSFILE;
+				}
+			}
+		}
+		close($fh);
+	}
+	return $pass;
+}
+
+sub conn {
+	my $self = shift;
+	my $repo = $self->{'fullrep'};
+	if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+		my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
+
+		my ($proxyhost,$proxyport);
+		if ($param && ($param =~ m/proxy=([^;]+)/)) {
+			$proxyhost = $1;
+			# Default proxyport, if not specified, is 8080.
+			$proxyport = 8080;
+			if ($ENV{"CVS_PROXY_PORT"}) {
+				$proxyport = $ENV{"CVS_PROXY_PORT"};
+			}
+			if ($param =~ m/proxyport=([^;]+)/) {
+				$proxyport = $1;
+			}
+		}
+		$repo ||= '/';
+
+		# if username is not explicit in CVSROOT, then use current user, as cvs would
+		$user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || "anonymous") unless $user;
+		my $rr2 = "-";
+		unless ($port) {
+			$rr2 = ":pserver:$user\@$serv:$repo";
+			$port=2401;
+		}
+		my $rr = ":pserver:$user\@$serv:$port$repo";
+
+		if ($pass) {
+			$pass = $self->_scramble($pass);
+		} else {
+			my @cvspass = ([$ENV{'HOME'}."/.cvspass", qr/\s/],
+				       [$ENV{'HOME'}."/.cvs/cvspass", qr/=/]);
+			my @loc = ();
+			foreach my $cvspass (@cvspass) {
+				my $p = find_password_entry($cvspass, $rr, $rr2);
+				if ($p) {
+					push @loc, $cvspass->[0];
+					$pass = $p;
+				}
+			}
+
+			if (1 < @loc) {
+				die("Multiple cvs password files have ".
+				    "entries for CVSROOT $opt_d: @loc");
+			} elsif (!$pass) {
+				$pass = "A";
+			}
+		}
+
+		my ($s, $rep);
+		if ($proxyhost) {
+
+			# Use a HTTP Proxy. Only works for HTTP proxies that
+			# don't require user authentication
+			#
+			# See: http://www.ietf.org/rfc/rfc2817.txt
+
+			$s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
+			die "Socket to $proxyhost: $!\n" unless defined $s;
+			$s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
+	                        or die "Write to $proxyhost: $!\n";
+	                $s->flush();
+
+			$rep = <$s>;
+
+			# The answer should look like 'HTTP/1.x 2yy ....'
+			if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
+				die "Proxy connect: $rep\n";
+			}
+			# Skip up to the empty line of the proxy server output
+			# including the response headers.
+			while ($rep = <$s>) {
+				last if (!defined $rep ||
+					 $rep eq "\n" ||
+					 $rep eq "\r\n");
+			}
+		} else {
+			$s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
+			die "Socket to $serv: $!\n" unless defined $s;
+		}
+
+		$s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
+			or die "Write to $serv: $!\n";
+		$s->flush();
+
+		$rep = <$s>;
+
+		if ($rep ne "I LOVE YOU\n") {
+			$rep="<unknown>" unless $rep;
+			die "AuthReply: $rep\n";
+		}
+		$self->{'socketo'} = $s;
+		$self->{'socketi'} = $s;
+	} else { # local or ext: Fork off our own cvs server.
+		my $pr = IO::Pipe->new();
+		my $pw = IO::Pipe->new();
+		my $pid = fork();
+		die "Fork: $!\n" unless defined $pid;
+		my $cvs = 'cvs';
+		$cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};
+		my $rsh = 'rsh';
+		$rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};
+
+		my @cvs = ($cvs, 'server');
+		my ($local, $user, $host);
+		$local = $repo =~ s/:local://;
+		if (!$local) {
+		    $repo =~ s/:ext://;
+		    $local = !($repo =~ s/^(?:([^\@:]+)\@)?([^:]+)://);
+		    ($user, $host) = ($1, $2);
+		}
+		if (!$local) {
+		    if ($user) {
+			unshift @cvs, $rsh, '-l', $user, $host;
+		    } else {
+			unshift @cvs, $rsh, $host;
+		    }
+		}
+
+		unless ($pid) {
+			$pr->writer();
+			$pw->reader();
+			dup2($pw->fileno(),0);
+			dup2($pr->fileno(),1);
+			$pr->close();
+			$pw->close();
+			exec(@cvs);
+		}
+		$pw->writer();
+		$pr->reader();
+		$self->{'socketo'} = $pw;
+		$self->{'socketi'} = $pr;
+	}
+	$self->{'socketo'}->write("Root $repo\n");
+
+	# Trial and error says that this probably is the minimum set
+	$self->{'socketo'}->write("Valid-responses ok error Valid-requests Mode M Mbinary E Checked-in Created Updated Merged Removed\n");
+
+	$self->{'socketo'}->write("valid-requests\n");
+	$self->{'socketo'}->flush();
+
+	my $rep=$self->readline();
+	die "Failed to read from server" unless defined $rep;
+	chomp($rep);
+	if ($rep !~ s/^Valid-requests\s*//) {
+		$rep="<unknown>" unless $rep;
+		die "Expected Valid-requests from server, but got: $rep\n";
+	}
+	chomp(my $res=$self->readline());
+	die "validReply: $res\n" if $res ne "ok";
+
+	$self->{'socketo'}->write("UseUnchanged\n") if $rep =~ /\bUseUnchanged\b/;
+	$self->{'repo'} = $repo;
+}
+
+sub readline {
+	my ($self) = @_;
+	return $self->{'socketi'}->getline();
+}
+
+sub _file {
+	# Request a file with a given revision.
+	# Trial and error says this is a good way to do it. :-/
+	my ($self,$fn,$rev) = @_;
+	$self->{'socketo'}->write("Argument -N\n") or return undef;
+	$self->{'socketo'}->write("Argument -P\n") or return undef;
+	# -kk: Linus' version doesn't use it - defaults to off
+	if ($opt_k) {
+	    $self->{'socketo'}->write("Argument -kk\n") or return undef;
+	}
+	$self->{'socketo'}->write("Argument -r\n") or return undef;
+	$self->{'socketo'}->write("Argument $rev\n") or return undef;
+	$self->{'socketo'}->write("Argument --\n") or return undef;
+	$self->{'socketo'}->write("Argument $self->{'subdir'}/$fn\n") or return undef;
+	$self->{'socketo'}->write("Directory .\n") or return undef;
+	$self->{'socketo'}->write("$self->{'repo'}\n") or return undef;
+	# $self->{'socketo'}->write("Sticky T1.0\n") or return undef;
+	$self->{'socketo'}->write("co\n") or return undef;
+	$self->{'socketo'}->flush() or return undef;
+	$self->{'lines'} = 0;
+	return 1;
+}
+sub _line {
+	# Read a line from the server.
+	# ... except that 'line' may be an entire file. ;-)
+	my ($self, $fh) = @_;
+	die "Not in lines" unless defined $self->{'lines'};
+
+	my $line;
+	my $res=0;
+	while (defined($line = $self->readline())) {
+		# M U gnupg-cvs-rep/AUTHORS
+		# Updated gnupg-cvs-rep/
+		# /daten/src/rsync/gnupg-cvs-rep/AUTHORS
+		# /AUTHORS/1.1///T1.1
+		# u=rw,g=rw,o=rw
+		# 0
+		# ok
+
+		if ($line =~ s/^(?:Created|Updated) //) {
+			$line = $self->readline(); # path
+			$line = $self->readline(); # Entries line
+			my $mode = $self->readline(); chomp $mode;
+			$self->{'mode'} = $mode;
+			defined (my $cnt = $self->readline())
+				or die "EOF from server after 'Changed'\n";
+			chomp $cnt;
+			die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
+			$line="";
+			$res = $self->_fetchfile($fh, $cnt);
+		} elsif ($line =~ s/^ //) {
+			print $fh $line;
+			$res += length($line);
+		} elsif ($line =~ /^M\b/) {
+			# output, do nothing
+		} elsif ($line =~ /^Mbinary\b/) {
+			my $cnt;
+			die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
+			chomp $cnt;
+			die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
+			$line="";
+			$res += $self->_fetchfile($fh, $cnt);
+		} else {
+			chomp $line;
+			if ($line eq "ok") {
+				# print STDERR "S: ok (".length($res).")\n";
+				return $res;
+			} elsif ($line =~ s/^E //) {
+				# print STDERR "S: $line\n";
+			} elsif ($line =~ /^(Remove-entry|Removed) /i) {
+				$line = $self->readline(); # filename
+				$line = $self->readline(); # OK
+				chomp $line;
+				die "Unknown: $line" if $line ne "ok";
+				return -1;
+			} else {
+				die "Unknown: $line\n";
+			}
+		}
+	}
+	return undef;
+}
+sub file {
+	my ($self,$fn,$rev) = @_;
+	my $res;
+
+	my ($fh, $name) = tempfile('gitcvs.XXXXXX',
+		    DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+	$self->_file($fn,$rev) and $res = $self->_line($fh);
+
+	if (!defined $res) {
+	    print STDERR "Server has gone away while fetching $fn $rev, retrying...\n";
+	    truncate $fh, 0;
+	    $self->conn();
+	    $self->_file($fn,$rev) or die "No file command send";
+	    $res = $self->_line($fh);
+	    die "Retry failed" unless defined $res;
+	}
+	close ($fh);
+
+	return ($name, $res);
+}
+sub _fetchfile {
+	my ($self, $fh, $cnt) = @_;
+	my $res = 0;
+	my $bufsize = 1024 * 1024;
+	while ($cnt) {
+	    if ($bufsize > $cnt) {
+		$bufsize = $cnt;
+	    }
+	    my $buf;
+	    my $num = $self->{'socketi'}->read($buf,$bufsize);
+	    die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
+	    print $fh $buf;
+	    $res += $num;
+	    $cnt -= $num;
+	}
+	return $res;
+}
+
+sub _scramble {
+	my ($self, $pass) = @_;
+	my $scrambled = "A";
+
+	return $scrambled unless $pass;
+
+	my $pass_len = length($pass);
+	my @pass_arr = split("", $pass);
+	my $i;
+
+	# from cvs/src/scramble.c
+	my @shifts = (
+		  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
+		 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+		114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
+		111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
+		 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
+		125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
+		 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
+		 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
+		225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
+		199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
+		174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
+		207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
+		192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
+		227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
+		182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
+		243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
+	);
+
+	for ($i = 0; $i < $pass_len; $i++) {
+		$scrambled .= pack("C", $shifts[ord($pass_arr[$i])]);
+	}
+
+	return $scrambled;
+}
+
+package main;
+
+my $cvs = CVSconn->new($opt_d, $cvs_tree);
+
+
+sub pdate($) {
+	my ($d) = @_;
+	m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
+		or die "Unparseable date: $d\n";
+	my $y=$1; $y-=1900 if $y>1900;
+	return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub pmode($) {
+	my ($mode) = @_;
+	my $m = 0;
+	my $mm = 0;
+	my $um = 0;
+	for my $x(split(//,$mode)) {
+		if ($x eq ",") {
+			$m |= $mm&$um;
+			$mm = 0;
+			$um = 0;
+		} elsif ($x eq "u") { $um |= 0700;
+		} elsif ($x eq "g") { $um |= 0070;
+		} elsif ($x eq "o") { $um |= 0007;
+		} elsif ($x eq "r") { $mm |= 0444;
+		} elsif ($x eq "w") { $mm |= 0222;
+		} elsif ($x eq "x") { $mm |= 0111;
+		} elsif ($x eq "=") { # do nothing
+		} else { die "Unknown mode: $mode\n";
+		}
+	}
+	$m |= $mm&$um;
+	return $m;
+}
+
+sub getwd() {
+	my $pwd = `pwd`;
+	chomp $pwd;
+	return $pwd;
+}
+
+sub is_sha1 {
+	my $s = shift;
+	return $s =~ /^[a-f0-9]{40}$/;
+}
+
+sub get_headref ($) {
+	my $name = shift;
+	my $r = `git rev-parse --verify '$name' 2>/dev/null`;
+	return undef unless $? == 0;
+	chomp $r;
+	return $r;
+}
+
+my $user_filename_prepend = '';
+sub munge_user_filename {
+	my $name = shift;
+	return File::Spec->file_name_is_absolute($name) ?
+		$name :
+		$user_filename_prepend . $name;
+}
+
+-d $git_tree
+	or mkdir($git_tree,0777)
+	or die "Could not create $git_tree: $!";
+if ($git_tree ne '.') {
+	$user_filename_prepend = getwd() . '/';
+	chdir($git_tree);
+}
+
+my $last_branch = "";
+my $orig_branch = "";
+my %branch_date;
+my $tip_at_start = undef;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+
+my %index; # holds filenames of one index per branch
+
+unless (-d $git_dir) {
+	system(qw(git init));
+	die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+	system(qw(git read-tree --empty));
+	die "Cannot init an empty tree: $?\n" if $?;
+
+	$last_branch = $opt_o;
+	$orig_branch = "";
+} else {
+	open(F, "-|", qw(git symbolic-ref HEAD)) or
+		die "Cannot run git symbolic-ref: $!\n";
+	chomp ($last_branch = <F>);
+	$last_branch = basename($last_branch);
+	close(F);
+	unless ($last_branch) {
+		warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+		$last_branch = "master";
+	}
+	$orig_branch = $last_branch;
+	$tip_at_start = `git rev-parse --verify HEAD`;
+
+	# Get the last import timestamps
+	my $fmt = '($ref, $author) = (%(refname), %(author));';
+	my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
+	open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
+	while (defined(my $entry = <H>)) {
+		my ($ref, $author);
+		eval($entry) || die "cannot eval refs list: $@";
+		my ($head) = ($ref =~ m|^$remote/(.*)|);
+		$author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
+		$branch_date{$head} = $1;
+	}
+	close(H);
+        if (!exists $branch_date{$opt_o}) {
+		die "Branch '$opt_o' does not exist.\n".
+		       "Either use the correct '-o branch' option,\n".
+		       "or import to a new repository.\n";
+        }
+}
+
+-d $git_dir
+	or die "Could not create git subdir ($git_dir).\n";
+
+# now we read (and possibly save) author-info as well
+-f "$git_dir/cvs-authors" and
+  read_author_info("$git_dir/cvs-authors");
+if ($opt_A) {
+	read_author_info(munge_user_filename($opt_A));
+	write_author_info("$git_dir/cvs-authors");
+}
+
+# open .git/cvs-revisions, if requested
+open my $revision_map, '>>', "$git_dir/cvs-revisions"
+    or die "Can't open $git_dir/cvs-revisions for appending: $!\n"
+	if defined $opt_R;
+
+
+#
+# run cvsps into a file unless we are getting
+# it passed as a file via $opt_P
+#
+my $cvspsfile;
+unless ($opt_P) {
+	print "Running cvsps...\n" if $opt_v;
+	my $pid = open(CVSPS,"-|");
+	my $cvspsfh;
+	die "Cannot fork: $!\n" unless defined $pid;
+	unless ($pid) {
+		my @opt;
+		@opt = split(/,/,$opt_p) if defined $opt_p;
+		unshift @opt, '-z', $opt_z if defined $opt_z;
+		unshift @opt, '-q'         unless defined $opt_v;
+		unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
+			push @opt, '--cvs-direct';
+		}
+		exec($cvsps2,"--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+		die "Could not start cvsps: $!\n";
+	}
+	($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
+					  DIR => File::Spec->tmpdir());
+	while (<CVSPS>) {
+	    print $cvspsfh $_;
+	}
+	close CVSPS;
+	$? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
+	close $cvspsfh;
+} else {
+	$cvspsfile = munge_user_filename($opt_P);
+}
+
+open(CVS, "<$cvspsfile") or die $!;
+
+## cvsps output:
+#---------------------
+#PatchSet 314
+#Date: 1999/09/18 13:03:59
+#Author: wkoch
+#Branch: STABLE-BRANCH-1-0
+#Ancestor branch: HEAD
+#Tag: (none)
+#Log:
+#    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch
+#Members:
+#	README:1.57->1.57.2.1
+#	VERSION:1.96->1.96.2.1
+#
+#---------------------
+
+my $state = 0;
+
+sub update_index (\@\@) {
+	my $old = shift;
+	my $new = shift;
+	open(my $fh, '|-', qw(git update-index -z --index-info))
+		or die "unable to open git update-index: $!";
+	print $fh
+		(map { "0 0000000000000000000000000000000000000000\t$_\0" }
+			@$old),
+		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
+			@$new)
+		or die "unable to write to git update-index: $!";
+	close $fh
+		or die "unable to write to git update-index: $!";
+	$? and die "git update-index reported error: $?";
+}
+
+sub write_tree () {
+	open(my $fh, '-|', qw(git write-tree))
+		or die "unable to open git write-tree: $!";
+	chomp(my $tree = <$fh>);
+	is_sha1($tree)
+		or die "Cannot get tree id ($tree): $!";
+	close($fh)
+		or die "Error running git write-tree: $?\n";
+	print "Tree ID $tree\n" if $opt_v;
+	return $tree;
+}
+
+my ($patchset,$date,$author_name,$author_email,$author_tz,$branch,$ancestor,$tag,$logmsg);
+my (@old,@new,@skipped,%ignorebranch,@commit_revisions);
+
+# commits that cvsps cannot place anywhere...
+$ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
+
+sub commit {
+	if ($branch eq $opt_o && !$index{branch} &&
+		!get_headref("$remote/$branch")) {
+	    # looks like an initial commit
+	    # use the index primed by git init
+	    $ENV{GIT_INDEX_FILE} = "$git_dir/index";
+	    $index{$branch} = "$git_dir/index";
+	} else {
+	    # use an index per branch to speed up
+	    # imports of projects with many branches
+	    unless ($index{$branch}) {
+		$index{$branch} = tmpnam();
+		$ENV{GIT_INDEX_FILE} = $index{$branch};
+		if ($ancestor) {
+		    system("git", "read-tree", "$remote/$ancestor");
+		} else {
+		    system("git", "read-tree", "$remote/$branch");
+		}
+		die "read-tree failed: $?\n" if $?;
+	    }
+	}
+        $ENV{GIT_INDEX_FILE} = $index{$branch};
+
+	update_index(@old, @new);
+	@old = @new = ();
+	my $tree = write_tree();
+	my $parent = get_headref("$remote/$last_branch");
+	print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
+
+	my @commit_args;
+	push @commit_args, ("-p", $parent) if $parent;
+
+	# loose detection of merges
+	# based on the commit msg
+	foreach my $rx (@mergerx) {
+		next unless $logmsg =~ $rx && $1;
+		my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
+		if (my $sha1 = get_headref("$remote/$mparent")) {
+			push @commit_args, '-p', "$remote/$mparent";
+			print "Merge parent branch: $mparent\n" if $opt_v;
+		}
+	}
+
+	set_timezone($author_tz);
+	my $commit_date = strftime("%s %z", localtime($date));
+	set_timezone('UTC');
+	$ENV{GIT_AUTHOR_NAME} = $author_name;
+	$ENV{GIT_AUTHOR_EMAIL} = $author_email;
+	$ENV{GIT_AUTHOR_DATE} = $commit_date;
+	$ENV{GIT_COMMITTER_NAME} = $author_name;
+	$ENV{GIT_COMMITTER_EMAIL} = $author_email;
+	$ENV{GIT_COMMITTER_DATE} = $commit_date;
+	my $pid = open2(my $commit_read, my $commit_write,
+		'git', 'commit-tree', $tree, @commit_args);
+
+	# compatibility with git2cvs
+	substr($logmsg,32767) = "" if length($logmsg) > 32767;
+	$logmsg =~ s/[\s\n]+\z//;
+
+	if (@skipped) {
+	    $logmsg .= "\n\n\nSKIPPED:\n\t";
+	    $logmsg .= join("\n\t", @skipped) . "\n";
+	    @skipped = ();
+	}
+
+	print($commit_write "$logmsg\n") && close($commit_write)
+		or die "Error writing to git commit-tree: $!\n";
+
+	print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
+	chomp(my $cid = <$commit_read>);
+	is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
+	print "Commit ID $cid\n" if $opt_v;
+	close($commit_read);
+
+	waitpid($pid,0);
+	die "Error running git commit-tree: $?\n" if $?;
+
+	system('git' , 'update-ref', "$remote/$branch", $cid) == 0
+		or die "Cannot write branch $branch for update: $!\n";
+
+	if ($revision_map) {
+		print $revision_map "@$_ $cid\n" for @commit_revisions;
+	}
+	@commit_revisions = ();
+
+	if ($tag) {
+	        my ($xtag) = $tag;
+		$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
+		$xtag =~ tr/_/\./ if ( $opt_u );
+		$xtag =~ s/[\/]/$opt_s/g;
+
+		# See refs.c for these rules.
+		# Tag cannot contain bad chars. (See bad_ref_char in refs.c.)
+		$xtag =~ s/[ ~\^:\\\*\?\[]//g;
+		# Other bad strings for tags:
+		# (See check_refname_component in refs.c.)
+		1 while $xtag =~ s/
+			(?: \.\.        # Tag cannot contain '..'.
+			|   \@{         # Tag cannot contain '@{'.
+			| ^ -           # Tag cannot begin with '-'.
+			|   \.lock $    # Tag cannot end with '.lock'.
+			| ^ \.          # Tag cannot begin...
+			|   \. $        # ...or end with '.'
+			)//xg;
+		# Tag cannot be empty.
+		if ($xtag eq '') {
+			warn("warning: ignoring tag '$tag'",
+			" with invalid tagname\n");
+			return;
+		}
+
+		if (system('git' , 'tag', '-f', $xtag, $cid) != 0) {
+			# We did our best to sanitize the tag, but still failed
+			# for whatever reason. Bail out, and give the user
+			# enough information to understand if/how we should
+			# improve the translation in the future.
+			if ($tag ne $xtag) {
+				print "Translated '$tag' tag to '$xtag'\n";
+			}
+			die "Cannot create tag $xtag: $!\n";
+		}
+
+		print "Created tag '$xtag' on '$branch'\n" if $opt_v;
+	}
+};
+
+my $commitcount = 1;
+while (<CVS>) {
+	chomp;
+	if ($state == 0 and /^-+$/) {
+		$state = 1;
+	} elsif ($state == 0) {
+		$state = 1;
+		redo;
+	} elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
+		$patchset = 0+$_;
+		$state=2;
+	} elsif ($state == 2 and s/^Date:\s+//) {
+		$date = pdate($_);
+		unless ($date) {
+			print STDERR "Could not parse date: $_\n";
+			$state=0;
+			next;
+		}
+		$state=3;
+	} elsif ($state == 3 and s/^Author:\s+//) {
+		$author_tz = "UTC";
+		s/\s+$//;
+		if (/^(.*?)\s+<(.*)>/) {
+		    ($author_name, $author_email) = ($1, $2);
+		} elsif ($conv_author_name{$_}) {
+			$author_name = $conv_author_name{$_};
+			$author_email = $conv_author_email{$_};
+			$author_tz = $conv_author_tz{$_} if ($conv_author_tz{$_});
+		} else {
+		    $author_name = $author_email = $_;
+		}
+		$state = 4;
+	} elsif ($state == 4 and s/^Branch:\s+//) {
+		s/\s+$//;
+		tr/_/\./ if ( $opt_u );
+		s/[\/]/$opt_s/g;
+		$branch = $_;
+		$state = 5;
+	} elsif ($state == 5 and s/^Ancestor branch:\s+//) {
+		s/\s+$//;
+		$ancestor = $_;
+		$ancestor = $opt_o if $ancestor eq "HEAD";
+		$state = 6;
+	} elsif ($state == 5) {
+		$ancestor = undef;
+		$state = 6;
+		redo;
+	} elsif ($state == 6 and s/^Tag:\s+//) {
+		s/\s+$//;
+		if ($_ eq "(none)") {
+			$tag = undef;
+		} else {
+			$tag = $_;
+		}
+		$state = 7;
+	} elsif ($state == 7 and /^Log:/) {
+		$logmsg = "";
+		$state = 8;
+	} elsif ($state == 8 and /^Members:/) {
+		$branch = $opt_o if $branch eq "HEAD";
+		if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
+			# skip
+			print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
+			$state = 11;
+			next;
+		}
+		if (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {
+			# skip if the commit is too recent
+			# given that the cvsps default fuzz is 300s, we give ourselves another
+			# 300s just in case -- this also prevents skipping commits
+			# due to server clock drift
+			print "skip patchset $patchset: $date too recent\n" if $opt_v;
+			$state = 11;
+			next;
+		}
+		if (exists $ignorebranch{$branch}) {
+			print STDERR "Skipping $branch\n";
+			$state = 11;
+			next;
+		}
+		if ($ancestor) {
+			if ($ancestor eq $branch) {
+				print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
+				$ancestor = $opt_o;
+			}
+			if (defined get_headref("$remote/$branch")) {
+				print STDERR "Branch $branch already exists!\n";
+				$state=11;
+				next;
+			}
+			my $id = get_headref("$remote/$ancestor");
+			if (!$id) {
+				print STDERR "Branch $ancestor does not exist!\n";
+				$ignorebranch{$branch} = 1;
+				$state=11;
+				next;
+			}
+
+			system(qw(git update-ref -m cvsimport),
+				"$remote/$branch", $id);
+			if($? != 0) {
+				print STDERR "Could not create branch $branch\n";
+				$ignorebranch{$branch} = 1;
+				$state=11;
+				next;
+			}
+		}
+		$last_branch = $branch if $branch ne $last_branch;
+		$state = 9;
+	} elsif ($state == 8) {
+		$logmsg .= "$_\n";
+	} elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
+#	VERSION:1.96->1.96.2.1
+		my $init = ($2 eq "INITIAL");
+		my $fn = $1;
+		my $rev = $3;
+		$fn =~ s#^/+##;
+		if ($opt_S && $fn =~ m/$opt_S/) {
+		    print "SKIPPING $fn v $rev\n";
+		    push(@skipped, $fn);
+		    next;
+		}
+		push @commit_revisions, [$fn, $rev];
+		print "Fetching $fn   v $rev\n" if $opt_v;
+		my ($tmpname, $size) = $cvs->file($fn,$rev);
+		if ($size == -1) {
+			push(@old,$fn);
+			print "Drop $fn\n" if $opt_v;
+		} else {
+			print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
+			my $pid = open(my $F, '-|');
+			die $! unless defined $pid;
+			if (!$pid) {
+			    exec("git", "hash-object", "-w", $tmpname)
+				or die "Cannot create object: $!\n";
+			}
+			my $sha = <$F>;
+			chomp $sha;
+			close $F;
+			my $mode = pmode($cvs->{'mode'});
+			push(@new,[$mode, $sha, $fn]); # may be resurrected!
+		}
+		unlink($tmpname);
+	} elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
+		my $fn = $1;
+		my $rev = $2;
+		$fn =~ s#^/+##;
+		push @commit_revisions, [$fn, $rev];
+		push(@old,$fn);
+		print "Delete $fn\n" if $opt_v;
+	} elsif ($state == 9 and /^\s*$/) {
+		$state = 10;
+	} elsif (($state == 9 or $state == 10) and /^-+$/) {
+		$commitcount++;
+		if ($opt_L && $commitcount > $opt_L) {
+			last;
+		}
+		commit();
+		if (($commitcount & 1023) == 0) {
+			system(qw(git repack -a -d));
+		}
+		$state = 1;
+	} elsif ($state == 11 and /^-+$/) {
+		$state = 1;
+	} elsif (/^-+$/) { # end of unknown-line processing
+		$state = 1;
+	} elsif ($state != 11) { # ignore stuff when skipping
+		print STDERR "* UNKNOWN LINE * $_\n";
+	}
+}
+commit() if $branch and $state != 11;
+
+unless ($opt_P) {
+	unlink($cvspsfile);
+}
+
+# The heuristic of repacking every 1024 commits can leave a
+# lot of unpacked data.  If there is more than 1MB worth of
+# not-packed objects, repack once more.
+my $line = `git count-objects`;
+if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
+  my ($n_objects, $kb) = ($1, $2);
+  1024 < $kb
+    and system(qw(git repack -a -d));
+}
+
+foreach my $git_index (values %index) {
+    if ($git_index ne "$git_dir/index") {
+	unlink($git_index);
+    }
+}
+
+if (defined $orig_git_index) {
+	$ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+	delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if ($orig_branch) {
+	print "DONE.\n" if $opt_v;
+	if ($opt_i) {
+		exit 0;
+	}
+	my $tip_at_end = `git rev-parse --verify HEAD`;
+	if ($tip_at_start ne $tip_at_end) {
+		for ($tip_at_start, $tip_at_end) { chomp; }
+		print "Fetched into the current branch.\n" if $opt_v;
+		system(qw(git read-tree -u -m),
+		       $tip_at_start, $tip_at_end);
+		die "Fast-forward update failed: $?\n" if $?;
+	}
+	else {
+		system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
+		die "Could not merge $opt_o into the current branch.\n" if $?;
+	}
+} else {
+	$orig_branch = "master";
+	print "DONE; creating $orig_branch branch\n" if $opt_v;
+	system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
+		unless defined get_headref('refs/heads/master');
+	system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
+		if ($opt_r && $opt_o ne 'HEAD');
+	system('git', 'update-ref', 'HEAD', "$orig_branch");
+	unless ($opt_i) {
+		system(qw(git checkout -f));
+		die "checkout failed: $?\n" if $?;
+	}
+}
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
deleted file mode 100755
index ad460a5..0000000
--- a/git-cvsimport.perl
+++ /dev/null
@@ -1,1179 +0,0 @@
-#!/usr/bin/perl
-
-# This tool is copyright (c) 2005, Matthias Urlichs.
-# It is released under the Gnu Public License, version 2.
-#
-# The basic idea is to aggregate CVS check-ins into related changes.
-# Fortunately, "cvsps" does that for us; all we have to do is to parse
-# its output.
-#
-# Checking out the files is done by a single long-running CVS connection
-# / server process.
-#
-# The head revision is on branch "origin" by default.
-# You can change that with the '-o' option.
-
-use 5.008;
-use strict;
-use warnings;
-use Getopt::Long;
-use File::Spec;
-use File::Temp qw(tempfile tmpnam);
-use File::Path qw(mkpath);
-use File::Basename qw(basename dirname);
-use Time::Local;
-use IO::Socket;
-use IO::Pipe;
-use POSIX qw(strftime tzset dup2 ENOENT);
-use IPC::Open2;
-
-$SIG{'PIPE'}="IGNORE";
-set_timezone('UTC');
-
-our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R);
-my (%conv_author_name, %conv_author_email, %conv_author_tz);
-
-my $cvsps2 = "@@CVSPS2_PATH@@";
-
-sub usage(;$) {
-	my $msg = shift;
-	print(STDERR "Error: $msg\n") if $msg;
-	print STDERR <<END;
-Usage: git cvsimport     # fetch/update GIT from CVS
-       [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
-       [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
-       [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
-       [-r remote] [-R] [CVS_module]
-END
-	exit(1);
-}
-
-sub read_author_info($) {
-	my ($file) = @_;
-	my $user;
-	open my $f, '<', "$file" or die("Failed to open $file: $!\n");
-
-	while (<$f>) {
-		# Expected format is this:
-		#   exon=Andreas Ericsson <ae@op5.se>
-		if (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/) {
-			$user = $1;
-			$conv_author_name{$user} = $2;
-			$conv_author_email{$user} = $3;
-		}
-		# or with an optional timezone:
-		#   spawn=Simon Pawn <spawn@frog-pond.org> America/Chicago
-		elsif (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*(\S+?)\s*$/) {
-			$user = $1;
-			$conv_author_name{$user} = $2;
-			$conv_author_email{$user} = $3;
-			$conv_author_tz{$user} = $4;
-		}
-		# However, we also read from CVSROOT/users format
-		# to ease migration.
-		elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) {
-			my $mapped;
-			($user, $mapped) = ($1, $3);
-			if ($mapped =~ /^\s*(.*?)\s*<(.*)>\s*$/) {
-				$conv_author_name{$user} = $1;
-				$conv_author_email{$user} = $2;
-			}
-			elsif ($mapped =~ /^<?(.*)>?$/) {
-				$conv_author_name{$user} = $user;
-				$conv_author_email{$user} = $1;
-			}
-		}
-		# NEEDSWORK: Maybe warn on unrecognized lines?
-	}
-	close ($f);
-}
-
-sub write_author_info($) {
-	my ($file) = @_;
-	open my $f, '>', $file or
-	  die("Failed to open $file for writing: $!");
-
-	foreach (keys %conv_author_name) {
-		print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>";
-		print $f " $conv_author_tz{$_}" if ($conv_author_tz{$_});
-		print $f "\n";
-	}
-	close ($f);
-}
-
-# Versions of perl before 5.10.0 may not automatically check $TZ each
-# time localtime is run (most platforms will do so only the first time).
-# We can work around this by using tzset() to update the internal
-# variable whenever we change the environment.
-sub set_timezone {
-	$ENV{TZ} = shift;
-	tzset();
-}
-
-# convert getopts specs for use by git config
-my %longmap = (
-	'A:' => 'authors-file',
-	'M:' => 'merge-regex',
-	'P:' => undef,
-	'R' => 'track-revisions',
-	'S:' => 'ignore-paths',
-);
-
-sub read_repo_config {
-	# Split the string between characters, unless there is a ':'
-	# So "abc:de" becomes ["a", "b", "c:", "d", "e"]
-	my @opts = split(/ *(?!:)/, shift);
-	foreach my $o (@opts) {
-		my $key = $o;
-		$key =~ s/://g;
-		my $arg = 'git config';
-		$arg .= ' --bool' if ($o !~ /:$/);
-		my $ckey = $key;
-
-		if (exists $longmap{$o}) {
-			# An uppercase option like -R cannot be
-			# expressed in the configuration, as the
-			# variable names are downcased.
-			$ckey = $longmap{$o};
-			next if (! defined $ckey);
-			$ckey =~ s/-//g;
-		}
-		chomp(my $tmp = `$arg --get cvsimport.$ckey`);
-		if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
-			no strict 'refs';
-			my $opt_name = "opt_" . $key;
-			if (!$$opt_name) {
-				$$opt_name = $tmp;
-			}
-		}
-	}
-}
-
-my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R";
-read_repo_config($opts);
-Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
-
-# turn the Getopt::Std specification in a Getopt::Long one,
-# with support for multiple -M options
-GetOptions( map { s/:/=s/; /M/ ? "$_\@" : $_ } split( /(?!:)/, $opts ) )
-    or usage();
-usage if $opt_h;
-
-if (@ARGV == 0) {
-		chomp(my $module = `git config --get cvsimport.module`);
-		push(@ARGV, $module) if $? == 0;
-}
-@ARGV <= 1 or usage("You can't specify more than one CVS module");
-
-if ($opt_d) {
-	$ENV{"CVSROOT"} = $opt_d;
-} elsif (-f 'CVS/Root') {
-	open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
-	$opt_d = <$f>;
-	chomp $opt_d;
-	close $f;
-	$ENV{"CVSROOT"} = $opt_d;
-} elsif ($ENV{"CVSROOT"}) {
-	$opt_d = $ENV{"CVSROOT"};
-} else {
-	usage("CVSROOT needs to be set");
-}
-$opt_s ||= "-";
-$opt_a ||= 0;
-
-my $git_tree = $opt_C;
-$git_tree ||= ".";
-
-my $remote;
-if (defined $opt_r) {
-	$remote = 'refs/remotes/' . $opt_r;
-	$opt_o ||= "master";
-} else {
-	$opt_o ||= "origin";
-	$remote = 'refs/heads';
-}
-
-my $cvs_tree;
-if ($#ARGV == 0) {
-	$cvs_tree = $ARGV[0];
-} elsif (-f 'CVS/Repository') {
-	open my $f, '<', 'CVS/Repository' or
-	    die 'Failed to open CVS/Repository';
-	$cvs_tree = <$f>;
-	chomp $cvs_tree;
-	close $f;
-} else {
-	usage("CVS module has to be specified");
-}
-
-our @mergerx = ();
-if ($opt_m) {
-	@mergerx = ( qr/\b(?:from|of|merge|merging|merged) ([-\w]+)/i );
-}
-if (@opt_M) {
-	push (@mergerx, map { qr/$_/ } @opt_M);
-}
-
-# Remember UTC of our starting time
-# we'll want to avoid importing commits
-# that are too recent
-our $starttime = time();
-
-select(STDERR); $|=1; select(STDOUT);
-
-
-package CVSconn;
-# Basic CVS dialog.
-# We're only interested in connecting and downloading, so ...
-
-use File::Spec;
-use File::Temp qw(tempfile);
-use POSIX qw(strftime dup2);
-
-sub new {
-	my ($what,$repo,$subdir) = @_;
-	$what=ref($what) if ref($what);
-
-	my $self = {};
-	$self->{'buffer'} = "";
-	bless($self,$what);
-
-	$repo =~ s#/+$##;
-	$self->{'fullrep'} = $repo;
-	$self->conn();
-
-	$self->{'subdir'} = $subdir;
-	$self->{'lines'} = undef;
-
-	return $self;
-}
-
-sub find_password_entry {
-	my ($cvspass, @cvsroot) = @_;
-	my ($file, $delim) = @$cvspass;
-	my $pass;
-	local ($_);
-
-	if (open(my $fh, $file)) {
-		# :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-		CVSPASSFILE:
-		while (<$fh>) {
-			chomp;
-			s/^\/\d+\s+//;
-			my ($w, $p) = split($delim,$_,2);
-			for my $cvsroot (@cvsroot) {
-				if ($w eq $cvsroot) {
-					$pass = $p;
-					last CVSPASSFILE;
-				}
-			}
-		}
-		close($fh);
-	}
-	return $pass;
-}
-
-sub conn {
-	my $self = shift;
-	my $repo = $self->{'fullrep'};
-	if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
-		my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
-
-		my ($proxyhost,$proxyport);
-		if ($param && ($param =~ m/proxy=([^;]+)/)) {
-			$proxyhost = $1;
-			# Default proxyport, if not specified, is 8080.
-			$proxyport = 8080;
-			if ($ENV{"CVS_PROXY_PORT"}) {
-				$proxyport = $ENV{"CVS_PROXY_PORT"};
-			}
-			if ($param =~ m/proxyport=([^;]+)/) {
-				$proxyport = $1;
-			}
-		}
-		$repo ||= '/';
-
-		# if username is not explicit in CVSROOT, then use current user, as cvs would
-		$user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || "anonymous") unless $user;
-		my $rr2 = "-";
-		unless ($port) {
-			$rr2 = ":pserver:$user\@$serv:$repo";
-			$port=2401;
-		}
-		my $rr = ":pserver:$user\@$serv:$port$repo";
-
-		if ($pass) {
-			$pass = $self->_scramble($pass);
-		} else {
-			my @cvspass = ([$ENV{'HOME'}."/.cvspass", qr/\s/],
-				       [$ENV{'HOME'}."/.cvs/cvspass", qr/=/]);
-			my @loc = ();
-			foreach my $cvspass (@cvspass) {
-				my $p = find_password_entry($cvspass, $rr, $rr2);
-				if ($p) {
-					push @loc, $cvspass->[0];
-					$pass = $p;
-				}
-			}
-
-			if (1 < @loc) {
-				die("Multiple cvs password files have ".
-				    "entries for CVSROOT $opt_d: @loc");
-			} elsif (!$pass) {
-				$pass = "A";
-			}
-		}
-
-		my ($s, $rep);
-		if ($proxyhost) {
-
-			# Use a HTTP Proxy. Only works for HTTP proxies that
-			# don't require user authentication
-			#
-			# See: http://www.ietf.org/rfc/rfc2817.txt
-
-			$s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
-			die "Socket to $proxyhost: $!\n" unless defined $s;
-			$s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
-	                        or die "Write to $proxyhost: $!\n";
-	                $s->flush();
-
-			$rep = <$s>;
-
-			# The answer should look like 'HTTP/1.x 2yy ....'
-			if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
-				die "Proxy connect: $rep\n";
-			}
-			# Skip up to the empty line of the proxy server output
-			# including the response headers.
-			while ($rep = <$s>) {
-				last if (!defined $rep ||
-					 $rep eq "\n" ||
-					 $rep eq "\r\n");
-			}
-		} else {
-			$s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
-			die "Socket to $serv: $!\n" unless defined $s;
-		}
-
-		$s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
-			or die "Write to $serv: $!\n";
-		$s->flush();
-
-		$rep = <$s>;
-
-		if ($rep ne "I LOVE YOU\n") {
-			$rep="<unknown>" unless $rep;
-			die "AuthReply: $rep\n";
-		}
-		$self->{'socketo'} = $s;
-		$self->{'socketi'} = $s;
-	} else { # local or ext: Fork off our own cvs server.
-		my $pr = IO::Pipe->new();
-		my $pw = IO::Pipe->new();
-		my $pid = fork();
-		die "Fork: $!\n" unless defined $pid;
-		my $cvs = 'cvs';
-		$cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};
-		my $rsh = 'rsh';
-		$rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};
-
-		my @cvs = ($cvs, 'server');
-		my ($local, $user, $host);
-		$local = $repo =~ s/:local://;
-		if (!$local) {
-		    $repo =~ s/:ext://;
-		    $local = !($repo =~ s/^(?:([^\@:]+)\@)?([^:]+)://);
-		    ($user, $host) = ($1, $2);
-		}
-		if (!$local) {
-		    if ($user) {
-			unshift @cvs, $rsh, '-l', $user, $host;
-		    } else {
-			unshift @cvs, $rsh, $host;
-		    }
-		}
-
-		unless ($pid) {
-			$pr->writer();
-			$pw->reader();
-			dup2($pw->fileno(),0);
-			dup2($pr->fileno(),1);
-			$pr->close();
-			$pw->close();
-			exec(@cvs);
-		}
-		$pw->writer();
-		$pr->reader();
-		$self->{'socketo'} = $pw;
-		$self->{'socketi'} = $pr;
-	}
-	$self->{'socketo'}->write("Root $repo\n");
-
-	# Trial and error says that this probably is the minimum set
-	$self->{'socketo'}->write("Valid-responses ok error Valid-requests Mode M Mbinary E Checked-in Created Updated Merged Removed\n");
-
-	$self->{'socketo'}->write("valid-requests\n");
-	$self->{'socketo'}->flush();
-
-	my $rep=$self->readline();
-	die "Failed to read from server" unless defined $rep;
-	chomp($rep);
-	if ($rep !~ s/^Valid-requests\s*//) {
-		$rep="<unknown>" unless $rep;
-		die "Expected Valid-requests from server, but got: $rep\n";
-	}
-	chomp(my $res=$self->readline());
-	die "validReply: $res\n" if $res ne "ok";
-
-	$self->{'socketo'}->write("UseUnchanged\n") if $rep =~ /\bUseUnchanged\b/;
-	$self->{'repo'} = $repo;
-}
-
-sub readline {
-	my ($self) = @_;
-	return $self->{'socketi'}->getline();
-}
-
-sub _file {
-	# Request a file with a given revision.
-	# Trial and error says this is a good way to do it. :-/
-	my ($self,$fn,$rev) = @_;
-	$self->{'socketo'}->write("Argument -N\n") or return undef;
-	$self->{'socketo'}->write("Argument -P\n") or return undef;
-	# -kk: Linus' version doesn't use it - defaults to off
-	if ($opt_k) {
-	    $self->{'socketo'}->write("Argument -kk\n") or return undef;
-	}
-	$self->{'socketo'}->write("Argument -r\n") or return undef;
-	$self->{'socketo'}->write("Argument $rev\n") or return undef;
-	$self->{'socketo'}->write("Argument --\n") or return undef;
-	$self->{'socketo'}->write("Argument $self->{'subdir'}/$fn\n") or return undef;
-	$self->{'socketo'}->write("Directory .\n") or return undef;
-	$self->{'socketo'}->write("$self->{'repo'}\n") or return undef;
-	# $self->{'socketo'}->write("Sticky T1.0\n") or return undef;
-	$self->{'socketo'}->write("co\n") or return undef;
-	$self->{'socketo'}->flush() or return undef;
-	$self->{'lines'} = 0;
-	return 1;
-}
-sub _line {
-	# Read a line from the server.
-	# ... except that 'line' may be an entire file. ;-)
-	my ($self, $fh) = @_;
-	die "Not in lines" unless defined $self->{'lines'};
-
-	my $line;
-	my $res=0;
-	while (defined($line = $self->readline())) {
-		# M U gnupg-cvs-rep/AUTHORS
-		# Updated gnupg-cvs-rep/
-		# /daten/src/rsync/gnupg-cvs-rep/AUTHORS
-		# /AUTHORS/1.1///T1.1
-		# u=rw,g=rw,o=rw
-		# 0
-		# ok
-
-		if ($line =~ s/^(?:Created|Updated) //) {
-			$line = $self->readline(); # path
-			$line = $self->readline(); # Entries line
-			my $mode = $self->readline(); chomp $mode;
-			$self->{'mode'} = $mode;
-			defined (my $cnt = $self->readline())
-				or die "EOF from server after 'Changed'\n";
-			chomp $cnt;
-			die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
-			$line="";
-			$res = $self->_fetchfile($fh, $cnt);
-		} elsif ($line =~ s/^ //) {
-			print $fh $line;
-			$res += length($line);
-		} elsif ($line =~ /^M\b/) {
-			# output, do nothing
-		} elsif ($line =~ /^Mbinary\b/) {
-			my $cnt;
-			die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
-			chomp $cnt;
-			die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
-			$line="";
-			$res += $self->_fetchfile($fh, $cnt);
-		} else {
-			chomp $line;
-			if ($line eq "ok") {
-				# print STDERR "S: ok (".length($res).")\n";
-				return $res;
-			} elsif ($line =~ s/^E //) {
-				# print STDERR "S: $line\n";
-			} elsif ($line =~ /^(Remove-entry|Removed) /i) {
-				$line = $self->readline(); # filename
-				$line = $self->readline(); # OK
-				chomp $line;
-				die "Unknown: $line" if $line ne "ok";
-				return -1;
-			} else {
-				die "Unknown: $line\n";
-			}
-		}
-	}
-	return undef;
-}
-sub file {
-	my ($self,$fn,$rev) = @_;
-	my $res;
-
-	my ($fh, $name) = tempfile('gitcvs.XXXXXX',
-		    DIR => File::Spec->tmpdir(), UNLINK => 1);
-
-	$self->_file($fn,$rev) and $res = $self->_line($fh);
-
-	if (!defined $res) {
-	    print STDERR "Server has gone away while fetching $fn $rev, retrying...\n";
-	    truncate $fh, 0;
-	    $self->conn();
-	    $self->_file($fn,$rev) or die "No file command send";
-	    $res = $self->_line($fh);
-	    die "Retry failed" unless defined $res;
-	}
-	close ($fh);
-
-	return ($name, $res);
-}
-sub _fetchfile {
-	my ($self, $fh, $cnt) = @_;
-	my $res = 0;
-	my $bufsize = 1024 * 1024;
-	while ($cnt) {
-	    if ($bufsize > $cnt) {
-		$bufsize = $cnt;
-	    }
-	    my $buf;
-	    my $num = $self->{'socketi'}->read($buf,$bufsize);
-	    die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
-	    print $fh $buf;
-	    $res += $num;
-	    $cnt -= $num;
-	}
-	return $res;
-}
-
-sub _scramble {
-	my ($self, $pass) = @_;
-	my $scrambled = "A";
-
-	return $scrambled unless $pass;
-
-	my $pass_len = length($pass);
-	my @pass_arr = split("", $pass);
-	my $i;
-
-	# from cvs/src/scramble.c
-	my @shifts = (
-		  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
-		 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
-		114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
-		111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
-		 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
-		125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
-		 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
-		 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
-		225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
-		199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
-		174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
-		207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
-		192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
-		227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
-		182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
-		243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
-	);
-
-	for ($i = 0; $i < $pass_len; $i++) {
-		$scrambled .= pack("C", $shifts[ord($pass_arr[$i])]);
-	}
-
-	return $scrambled;
-}
-
-package main;
-
-my $cvs = CVSconn->new($opt_d, $cvs_tree);
-
-
-sub pdate($) {
-	my ($d) = @_;
-	m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
-		or die "Unparseable date: $d\n";
-	my $y=$1; $y-=1900 if $y>1900;
-	return timegm($6||0,$5,$4,$3,$2-1,$y);
-}
-
-sub pmode($) {
-	my ($mode) = @_;
-	my $m = 0;
-	my $mm = 0;
-	my $um = 0;
-	for my $x(split(//,$mode)) {
-		if ($x eq ",") {
-			$m |= $mm&$um;
-			$mm = 0;
-			$um = 0;
-		} elsif ($x eq "u") { $um |= 0700;
-		} elsif ($x eq "g") { $um |= 0070;
-		} elsif ($x eq "o") { $um |= 0007;
-		} elsif ($x eq "r") { $mm |= 0444;
-		} elsif ($x eq "w") { $mm |= 0222;
-		} elsif ($x eq "x") { $mm |= 0111;
-		} elsif ($x eq "=") { # do nothing
-		} else { die "Unknown mode: $mode\n";
-		}
-	}
-	$m |= $mm&$um;
-	return $m;
-}
-
-sub getwd() {
-	my $pwd = `pwd`;
-	chomp $pwd;
-	return $pwd;
-}
-
-sub is_sha1 {
-	my $s = shift;
-	return $s =~ /^[a-f0-9]{40}$/;
-}
-
-sub get_headref ($) {
-	my $name = shift;
-	my $r = `git rev-parse --verify '$name' 2>/dev/null`;
-	return undef unless $? == 0;
-	chomp $r;
-	return $r;
-}
-
-my $user_filename_prepend = '';
-sub munge_user_filename {
-	my $name = shift;
-	return File::Spec->file_name_is_absolute($name) ?
-		$name :
-		$user_filename_prepend . $name;
-}
-
--d $git_tree
-	or mkdir($git_tree,0777)
-	or die "Could not create $git_tree: $!";
-if ($git_tree ne '.') {
-	$user_filename_prepend = getwd() . '/';
-	chdir($git_tree);
-}
-
-my $last_branch = "";
-my $orig_branch = "";
-my %branch_date;
-my $tip_at_start = undef;
-
-my $git_dir = $ENV{"GIT_DIR"} || ".git";
-$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
-$ENV{"GIT_DIR"} = $git_dir;
-my $orig_git_index;
-$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
-
-my %index; # holds filenames of one index per branch
-
-unless (-d $git_dir) {
-	system(qw(git init));
-	die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-	system(qw(git read-tree --empty));
-	die "Cannot init an empty tree: $?\n" if $?;
-
-	$last_branch = $opt_o;
-	$orig_branch = "";
-} else {
-	open(F, "-|", qw(git symbolic-ref HEAD)) or
-		die "Cannot run git symbolic-ref: $!\n";
-	chomp ($last_branch = <F>);
-	$last_branch = basename($last_branch);
-	close(F);
-	unless ($last_branch) {
-		warn "Cannot read the last branch name: $! -- assuming 'master'\n";
-		$last_branch = "master";
-	}
-	$orig_branch = $last_branch;
-	$tip_at_start = `git rev-parse --verify HEAD`;
-
-	# Get the last import timestamps
-	my $fmt = '($ref, $author) = (%(refname), %(author));';
-	my @cmd = ('git', 'for-each-ref', '--perl', "--format=$fmt", $remote);
-	open(H, "-|", @cmd) or die "Cannot run git for-each-ref: $!\n";
-	while (defined(my $entry = <H>)) {
-		my ($ref, $author);
-		eval($entry) || die "cannot eval refs list: $@";
-		my ($head) = ($ref =~ m|^$remote/(.*)|);
-		$author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
-		$branch_date{$head} = $1;
-	}
-	close(H);
-        if (!exists $branch_date{$opt_o}) {
-		die "Branch '$opt_o' does not exist.\n".
-		       "Either use the correct '-o branch' option,\n".
-		       "or import to a new repository.\n";
-        }
-}
-
--d $git_dir
-	or die "Could not create git subdir ($git_dir).\n";
-
-# now we read (and possibly save) author-info as well
--f "$git_dir/cvs-authors" and
-  read_author_info("$git_dir/cvs-authors");
-if ($opt_A) {
-	read_author_info(munge_user_filename($opt_A));
-	write_author_info("$git_dir/cvs-authors");
-}
-
-# open .git/cvs-revisions, if requested
-open my $revision_map, '>>', "$git_dir/cvs-revisions"
-    or die "Can't open $git_dir/cvs-revisions for appending: $!\n"
-	if defined $opt_R;
-
-
-#
-# run cvsps into a file unless we are getting
-# it passed as a file via $opt_P
-#
-my $cvspsfile;
-unless ($opt_P) {
-	print "Running cvsps...\n" if $opt_v;
-	my $pid = open(CVSPS,"-|");
-	my $cvspsfh;
-	die "Cannot fork: $!\n" unless defined $pid;
-	unless ($pid) {
-		my @opt;
-		@opt = split(/,/,$opt_p) if defined $opt_p;
-		unshift @opt, '-z', $opt_z if defined $opt_z;
-		unshift @opt, '-q'         unless defined $opt_v;
-		unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
-			push @opt, '--cvs-direct';
-		}
-		exec($cvsps2,"--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
-		die "Could not start cvsps: $!\n";
-	}
-	($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
-					  DIR => File::Spec->tmpdir());
-	while (<CVSPS>) {
-	    print $cvspsfh $_;
-	}
-	close CVSPS;
-	$? == 0 or die "git cvsimport: fatal: cvsps reported error\n";
-	close $cvspsfh;
-} else {
-	$cvspsfile = munge_user_filename($opt_P);
-}
-
-open(CVS, "<$cvspsfile") or die $!;
-
-## cvsps output:
-#---------------------
-#PatchSet 314
-#Date: 1999/09/18 13:03:59
-#Author: wkoch
-#Branch: STABLE-BRANCH-1-0
-#Ancestor branch: HEAD
-#Tag: (none)
-#Log:
-#    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch
-#Members:
-#	README:1.57->1.57.2.1
-#	VERSION:1.96->1.96.2.1
-#
-#---------------------
-
-my $state = 0;
-
-sub update_index (\@\@) {
-	my $old = shift;
-	my $new = shift;
-	open(my $fh, '|-', qw(git update-index -z --index-info))
-		or die "unable to open git update-index: $!";
-	print $fh
-		(map { "0 0000000000000000000000000000000000000000\t$_\0" }
-			@$old),
-		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
-			@$new)
-		or die "unable to write to git update-index: $!";
-	close $fh
-		or die "unable to write to git update-index: $!";
-	$? and die "git update-index reported error: $?";
-}
-
-sub write_tree () {
-	open(my $fh, '-|', qw(git write-tree))
-		or die "unable to open git write-tree: $!";
-	chomp(my $tree = <$fh>);
-	is_sha1($tree)
-		or die "Cannot get tree id ($tree): $!";
-	close($fh)
-		or die "Error running git write-tree: $?\n";
-	print "Tree ID $tree\n" if $opt_v;
-	return $tree;
-}
-
-my ($patchset,$date,$author_name,$author_email,$author_tz,$branch,$ancestor,$tag,$logmsg);
-my (@old,@new,@skipped,%ignorebranch,@commit_revisions);
-
-# commits that cvsps cannot place anywhere...
-$ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
-
-sub commit {
-	if ($branch eq $opt_o && !$index{branch} &&
-		!get_headref("$remote/$branch")) {
-	    # looks like an initial commit
-	    # use the index primed by git init
-	    $ENV{GIT_INDEX_FILE} = "$git_dir/index";
-	    $index{$branch} = "$git_dir/index";
-	} else {
-	    # use an index per branch to speed up
-	    # imports of projects with many branches
-	    unless ($index{$branch}) {
-		$index{$branch} = tmpnam();
-		$ENV{GIT_INDEX_FILE} = $index{$branch};
-		if ($ancestor) {
-		    system("git", "read-tree", "$remote/$ancestor");
-		} else {
-		    system("git", "read-tree", "$remote/$branch");
-		}
-		die "read-tree failed: $?\n" if $?;
-	    }
-	}
-        $ENV{GIT_INDEX_FILE} = $index{$branch};
-
-	update_index(@old, @new);
-	@old = @new = ();
-	my $tree = write_tree();
-	my $parent = get_headref("$remote/$last_branch");
-	print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
-
-	my @commit_args;
-	push @commit_args, ("-p", $parent) if $parent;
-
-	# loose detection of merges
-	# based on the commit msg
-	foreach my $rx (@mergerx) {
-		next unless $logmsg =~ $rx && $1;
-		my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
-		if (my $sha1 = get_headref("$remote/$mparent")) {
-			push @commit_args, '-p', "$remote/$mparent";
-			print "Merge parent branch: $mparent\n" if $opt_v;
-		}
-	}
-
-	set_timezone($author_tz);
-	my $commit_date = strftime("%s %z", localtime($date));
-	set_timezone('UTC');
-	$ENV{GIT_AUTHOR_NAME} = $author_name;
-	$ENV{GIT_AUTHOR_EMAIL} = $author_email;
-	$ENV{GIT_AUTHOR_DATE} = $commit_date;
-	$ENV{GIT_COMMITTER_NAME} = $author_name;
-	$ENV{GIT_COMMITTER_EMAIL} = $author_email;
-	$ENV{GIT_COMMITTER_DATE} = $commit_date;
-	my $pid = open2(my $commit_read, my $commit_write,
-		'git', 'commit-tree', $tree, @commit_args);
-
-	# compatibility with git2cvs
-	substr($logmsg,32767) = "" if length($logmsg) > 32767;
-	$logmsg =~ s/[\s\n]+\z//;
-
-	if (@skipped) {
-	    $logmsg .= "\n\n\nSKIPPED:\n\t";
-	    $logmsg .= join("\n\t", @skipped) . "\n";
-	    @skipped = ();
-	}
-
-	print($commit_write "$logmsg\n") && close($commit_write)
-		or die "Error writing to git commit-tree: $!\n";
-
-	print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
-	chomp(my $cid = <$commit_read>);
-	is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
-	print "Commit ID $cid\n" if $opt_v;
-	close($commit_read);
-
-	waitpid($pid,0);
-	die "Error running git commit-tree: $?\n" if $?;
-
-	system('git' , 'update-ref', "$remote/$branch", $cid) == 0
-		or die "Cannot write branch $branch for update: $!\n";
-
-	if ($revision_map) {
-		print $revision_map "@$_ $cid\n" for @commit_revisions;
-	}
-	@commit_revisions = ();
-
-	if ($tag) {
-	        my ($xtag) = $tag;
-		$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
-		$xtag =~ tr/_/\./ if ( $opt_u );
-		$xtag =~ s/[\/]/$opt_s/g;
-
-		# See refs.c for these rules.
-		# Tag cannot contain bad chars. (See bad_ref_char in refs.c.)
-		$xtag =~ s/[ ~\^:\\\*\?\[]//g;
-		# Other bad strings for tags:
-		# (See check_refname_component in refs.c.)
-		1 while $xtag =~ s/
-			(?: \.\.        # Tag cannot contain '..'.
-			|   \@{         # Tag cannot contain '@{'.
-			| ^ -           # Tag cannot begin with '-'.
-			|   \.lock $    # Tag cannot end with '.lock'.
-			| ^ \.          # Tag cannot begin...
-			|   \. $        # ...or end with '.'
-			)//xg;
-		# Tag cannot be empty.
-		if ($xtag eq '') {
-			warn("warning: ignoring tag '$tag'",
-			" with invalid tagname\n");
-			return;
-		}
-
-		if (system('git' , 'tag', '-f', $xtag, $cid) != 0) {
-			# We did our best to sanitize the tag, but still failed
-			# for whatever reason. Bail out, and give the user
-			# enough information to understand if/how we should
-			# improve the translation in the future.
-			if ($tag ne $xtag) {
-				print "Translated '$tag' tag to '$xtag'\n";
-			}
-			die "Cannot create tag $xtag: $!\n";
-		}
-
-		print "Created tag '$xtag' on '$branch'\n" if $opt_v;
-	}
-};
-
-my $commitcount = 1;
-while (<CVS>) {
-	chomp;
-	if ($state == 0 and /^-+$/) {
-		$state = 1;
-	} elsif ($state == 0) {
-		$state = 1;
-		redo;
-	} elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
-		$patchset = 0+$_;
-		$state=2;
-	} elsif ($state == 2 and s/^Date:\s+//) {
-		$date = pdate($_);
-		unless ($date) {
-			print STDERR "Could not parse date: $_\n";
-			$state=0;
-			next;
-		}
-		$state=3;
-	} elsif ($state == 3 and s/^Author:\s+//) {
-		$author_tz = "UTC";
-		s/\s+$//;
-		if (/^(.*?)\s+<(.*)>/) {
-		    ($author_name, $author_email) = ($1, $2);
-		} elsif ($conv_author_name{$_}) {
-			$author_name = $conv_author_name{$_};
-			$author_email = $conv_author_email{$_};
-			$author_tz = $conv_author_tz{$_} if ($conv_author_tz{$_});
-		} else {
-		    $author_name = $author_email = $_;
-		}
-		$state = 4;
-	} elsif ($state == 4 and s/^Branch:\s+//) {
-		s/\s+$//;
-		tr/_/\./ if ( $opt_u );
-		s/[\/]/$opt_s/g;
-		$branch = $_;
-		$state = 5;
-	} elsif ($state == 5 and s/^Ancestor branch:\s+//) {
-		s/\s+$//;
-		$ancestor = $_;
-		$ancestor = $opt_o if $ancestor eq "HEAD";
-		$state = 6;
-	} elsif ($state == 5) {
-		$ancestor = undef;
-		$state = 6;
-		redo;
-	} elsif ($state == 6 and s/^Tag:\s+//) {
-		s/\s+$//;
-		if ($_ eq "(none)") {
-			$tag = undef;
-		} else {
-			$tag = $_;
-		}
-		$state = 7;
-	} elsif ($state == 7 and /^Log:/) {
-		$logmsg = "";
-		$state = 8;
-	} elsif ($state == 8 and /^Members:/) {
-		$branch = $opt_o if $branch eq "HEAD";
-		if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
-			# skip
-			print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
-			$state = 11;
-			next;
-		}
-		if (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {
-			# skip if the commit is too recent
-			# given that the cvsps default fuzz is 300s, we give ourselves another
-			# 300s just in case -- this also prevents skipping commits
-			# due to server clock drift
-			print "skip patchset $patchset: $date too recent\n" if $opt_v;
-			$state = 11;
-			next;
-		}
-		if (exists $ignorebranch{$branch}) {
-			print STDERR "Skipping $branch\n";
-			$state = 11;
-			next;
-		}
-		if ($ancestor) {
-			if ($ancestor eq $branch) {
-				print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
-				$ancestor = $opt_o;
-			}
-			if (defined get_headref("$remote/$branch")) {
-				print STDERR "Branch $branch already exists!\n";
-				$state=11;
-				next;
-			}
-			my $id = get_headref("$remote/$ancestor");
-			if (!$id) {
-				print STDERR "Branch $ancestor does not exist!\n";
-				$ignorebranch{$branch} = 1;
-				$state=11;
-				next;
-			}
-
-			system(qw(git update-ref -m cvsimport),
-				"$remote/$branch", $id);
-			if($? != 0) {
-				print STDERR "Could not create branch $branch\n";
-				$ignorebranch{$branch} = 1;
-				$state=11;
-				next;
-			}
-		}
-		$last_branch = $branch if $branch ne $last_branch;
-		$state = 9;
-	} elsif ($state == 8) {
-		$logmsg .= "$_\n";
-	} elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
-#	VERSION:1.96->1.96.2.1
-		my $init = ($2 eq "INITIAL");
-		my $fn = $1;
-		my $rev = $3;
-		$fn =~ s#^/+##;
-		if ($opt_S && $fn =~ m/$opt_S/) {
-		    print "SKIPPING $fn v $rev\n";
-		    push(@skipped, $fn);
-		    next;
-		}
-		push @commit_revisions, [$fn, $rev];
-		print "Fetching $fn   v $rev\n" if $opt_v;
-		my ($tmpname, $size) = $cvs->file($fn,$rev);
-		if ($size == -1) {
-			push(@old,$fn);
-			print "Drop $fn\n" if $opt_v;
-		} else {
-			print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
-			my $pid = open(my $F, '-|');
-			die $! unless defined $pid;
-			if (!$pid) {
-			    exec("git", "hash-object", "-w", $tmpname)
-				or die "Cannot create object: $!\n";
-			}
-			my $sha = <$F>;
-			chomp $sha;
-			close $F;
-			my $mode = pmode($cvs->{'mode'});
-			push(@new,[$mode, $sha, $fn]); # may be resurrected!
-		}
-		unlink($tmpname);
-	} elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
-		my $fn = $1;
-		my $rev = $2;
-		$fn =~ s#^/+##;
-		push @commit_revisions, [$fn, $rev];
-		push(@old,$fn);
-		print "Delete $fn\n" if $opt_v;
-	} elsif ($state == 9 and /^\s*$/) {
-		$state = 10;
-	} elsif (($state == 9 or $state == 10) and /^-+$/) {
-		$commitcount++;
-		if ($opt_L && $commitcount > $opt_L) {
-			last;
-		}
-		commit();
-		if (($commitcount & 1023) == 0) {
-			system(qw(git repack -a -d));
-		}
-		$state = 1;
-	} elsif ($state == 11 and /^-+$/) {
-		$state = 1;
-	} elsif (/^-+$/) { # end of unknown-line processing
-		$state = 1;
-	} elsif ($state != 11) { # ignore stuff when skipping
-		print STDERR "* UNKNOWN LINE * $_\n";
-	}
-}
-commit() if $branch and $state != 11;
-
-unless ($opt_P) {
-	unlink($cvspsfile);
-}
-
-# The heuristic of repacking every 1024 commits can leave a
-# lot of unpacked data.  If there is more than 1MB worth of
-# not-packed objects, repack once more.
-my $line = `git count-objects`;
-if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
-  my ($n_objects, $kb) = ($1, $2);
-  1024 < $kb
-    and system(qw(git repack -a -d));
-}
-
-foreach my $git_index (values %index) {
-    if ($git_index ne "$git_dir/index") {
-	unlink($git_index);
-    }
-}
-
-if (defined $orig_git_index) {
-	$ENV{GIT_INDEX_FILE} = $orig_git_index;
-} else {
-	delete $ENV{GIT_INDEX_FILE};
-}
-
-# Now switch back to the branch we were in before all of this happened
-if ($orig_branch) {
-	print "DONE.\n" if $opt_v;
-	if ($opt_i) {
-		exit 0;
-	}
-	my $tip_at_end = `git rev-parse --verify HEAD`;
-	if ($tip_at_start ne $tip_at_end) {
-		for ($tip_at_start, $tip_at_end) { chomp; }
-		print "Fetched into the current branch.\n" if $opt_v;
-		system(qw(git read-tree -u -m),
-		       $tip_at_start, $tip_at_end);
-		die "Fast-forward update failed: $?\n" if $?;
-	}
-	else {
-		system(qw(git merge cvsimport HEAD), "$remote/$opt_o");
-		die "Could not merge $opt_o into the current branch.\n" if $?;
-	}
-} else {
-	$orig_branch = "master";
-	print "DONE; creating $orig_branch branch\n" if $opt_v;
-	system("git", "update-ref", "refs/heads/master", "$remote/$opt_o")
-		unless defined get_headref('refs/heads/master');
-	system("git", "symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
-		if ($opt_r && $opt_o ne 'HEAD');
-	system('git', 'update-ref', 'HEAD', "$orig_branch");
-	unless ($opt_i) {
-		system(qw(git checkout -f));
-		die "checkout failed: $?\n" if $?;
-	}
-}
diff --git a/git-cvsimport.sh b/git-cvsimport.sh
new file mode 100755
index 0000000..71ba11d
--- /dev/null
+++ b/git-cvsimport.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+GIT_CVSPS_VERSION=2
+
+exec git cvsimport-$GIT_CVSPS_VERSION "$@"
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH 3/3] cvsimport: start adding cvsps 3.x support
  2013-01-14  1:40       ` [PATCH 0/3] A smoother transition plan for cvsimport Junio C Hamano
  2013-01-14  1:40         ` [PATCH 1/3] cvsimport: allow setting a custom cvsps (2.x) program name Junio C Hamano
  2013-01-14  1:40         ` [PATCH 2/3] cvsimport: introduce a version-switch wrapper Junio C Hamano
@ 2013-01-14  1:40         ` Junio C Hamano
  2013-01-15  6:19           ` Chris Rorvick
  2 siblings, 1 reply; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  1:40 UTC (permalink / raw)
  To: git; +Cc: jrnieder, mhagger, esr, chris

The new cvsps 3.x series lacks support of some options cvsps 2.x
series had and used by cvsimport-2; add a replacement program from
the author of cvsps 3.x and allow users to choose it by setting the
GIT_CVSPS_VERSION environment variable to 3.  We would later add
support to auto-detect the version of installed cvsps to this code
when the environment variable is not set.

Note that in this step, cvsimport-3 that relies on cvsps 3.x does
not have any test coverage.  As cvsimport-3 supports most of the
command line options cvsimport-2, we should be able to tweak some of
t96xx tests and run them with GIT_CVSPS_VERSION set to both 2 and 3,
but that is a topic of later patches that should come on top.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile           |  18 ++-
 git-cvsimport-3.py | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 git-cvsimport.sh   |   4 +-
 3 files changed, 359 insertions(+), 7 deletions(-)
 create mode 100755 git-cvsimport-3.py

diff --git a/Makefile b/Makefile
index b022db2..060cdc2 100644
--- a/Makefile
+++ b/Makefile
@@ -470,8 +470,9 @@ SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
 SCRIPT_PERL += git-svn.perl
 
-SCRIPT_PYTHON += git-remote-testpy.py
+SCRIPT_PYTHON += git-cvsimport-3.py
 SCRIPT_PYTHON += git-p4.py
+SCRIPT_PYTHON += git-remote-testpy.py
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
 	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
@@ -575,8 +576,11 @@ endif
 ifndef CVSPS2_PATH
 	CVSPS2_PATH = cvsps
 endif
+ifndef CVSPS3_PATH
+	CVSPS3_PATH = cvsps
+endif
 
-export PERL_PATH PYTHON_PATH CVSPS2_PATH
+export PERL_PATH PYTHON_PATH CVSPS2_PATH CVSPS3_PATH
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
@@ -1515,6 +1519,7 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 CVSPS2_PATH_SQ = $(subst ','\'',$(CVSPS2_PATH))
+CVSPS3_PATH_SQ = $(subst ','\'',$(CVSPS3_PATH))
 DIFF_SQ = $(subst ','\'',$(DIFF))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1757,12 +1762,15 @@ ifndef NO_PYTHON
 $(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
 $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
 	$(QUIET_GEN)$(RM) $@ $@+ && \
-	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
+	INSTLIBDIR_SQ=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
 		--no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
-		instlibdir` && \
+		instlibdir | \
+		sed -e "s/'/'\''/g"` && \
+	echo "InstLibDir is <$$INSTLIBDIR_SQ>" && \
 	sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
 	    -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
-	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+	    -e 's|@@CVSPS3_PATH@@|$(CVSPS3_PATH_SQ)|g' \
+	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR_SQ"'|g' \
 	    $@.py >$@+ && \
 	chmod +x $@+ && \
 	mv $@+ $@
diff --git a/git-cvsimport-3.py b/git-cvsimport-3.py
new file mode 100755
index 0000000..57f13b7
--- /dev/null
+++ b/git-cvsimport-3.py
@@ -0,0 +1,344 @@
+#!/usr/bin/env python
+#
+# Import CVS history into git
+#
+# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.
+#
+# By Eric S. Raymond <esr@thyrsus.com>, December 2012
+# May be redistributed under the license of the git project.
+
+import sys
+
+if sys.hexversion < 0x02060000:
+    sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+    sys.exit(1)
+
+import os, getopt, subprocess, tempfile, shutil
+
+DEBUG_COMMANDS = 1
+
+class Fatal(Exception):
+    "Unrecoverable error."
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def do_or_die(dcmd, legend=""):
+    "Either execute a command or raise a fatal exception."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        retcode = subprocess.call(dcmd, shell=True)
+        if retcode < 0:
+            raise Fatal("git cvsimport: child was terminated by signal %d." % -retcode)
+        elif retcode != 0:
+            raise Fatal("git cvsimport: child returned %d." % retcode)
+    except (OSError, IOError) as e:
+        raise Fatal("git cvsimport: execution of %s%s failed: %s" % (dcmd, legend, e))
+
+def capture_or_die(dcmd, legend=""):
+    "Either execute a command and capture its output or die."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        return subprocess.check_output(dcmd, shell=True)
+    except subprocess.CalledProcessError as e:
+        if e.returncode < 0:
+            sys.stderr.write("git cvsimport: child was terminated by signal %d." % -e.returncode)
+        elif e.returncode != 0:
+            sys.stderr.write("git cvsimport: child returned %d." % e.returncode)
+        sys.exit(1)
+
+class cvsps:
+    "Method class for cvsps back end."
+
+    cvsps = "@@CVSPS3_PATH@@"
+    def __init__(self):
+        self.opts = ""
+        self.revmap = None
+    def set_repo(self, val):
+        "Set the repository root option."
+        if not val.startswith(":"):
+            if not val.startswith(os.sep):
+                val = os.path.abspath(val)
+            val = ":local:" + val
+        self.opts += " --root '%s'" % val
+    def set_authormap(self, val):
+        "Set the author-map file."
+        self.opts += " -A '%s'" % val
+    def set_fuzz(self, val):
+        "Set the commit-similarity window."
+        self.opts += " -z %s" % val
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " -k"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " -n -f '%s'" % val
+    def set_after(self, val):
+        "Set a date threshold for incremental import."
+        self.opts += " -d '%s'" % val
+    def set_revmap(self, val):
+        "Set the file to which the engine should dump a reference map."
+        self.revmap = val
+        self.opts += " -R '%s'" % self.revmap
+    def set_module(self, val):
+        "Set the module to query."
+        self.opts += " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return self.cvsps + "--fast-export " + self.opts
+
+class cvs2git:
+    "Method class for cvs2git back end."
+    def __init__(self):
+        self.opts = ""
+        self.modulepath = "."
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author maping is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        sys.stderr.write("git cvsimport: cvs2git must run within a repository checkout directory.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        sys.stderr.write("git cvsimport: fuzz setting is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " --keywords-off"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " --exclude='%s'" % val
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        sys.stderr.write("git cvsimport: incremental import is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, val):
+        "Set the module to query."
+        self.modulepath = " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "(cvs2git --username=git-cvsimport --quiet --quiet --blobfile={0} --dumpfile={1} {2} {3} && cat {0} {1} && rm {0} {1})".format(tempfile.mkstemp()[1], tempfile.mkstemp()[1], self.opts, self.modulepath)
+
+class filesource:
+    "Method class for file-source back end."
+    def __init__(self, filename):
+        self.filename = filename
+    def __complain(self, legend):
+        sys.stderr.write("git cvsimport: %s with file source.\n" % legend)
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        self.__complain("repository can't be set")
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author mapping is not supported with filesource.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        self.__complain("fuzz can't be set")
+    def set_nokeywords(self, _val):
+        "Suppress CVS keyword expansion."
+        self.__complain("keyword suppression can't be set")
+    def add_opts(self, _val):
+        "Add options to the engine command line."
+        self.__complain("other options can't be set")
+    def set_exclusion(self, _val):
+        "Set a file exclusion regexp."
+        self.__complain("exclusions can't be set")
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        pass
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, _val):
+        "Set the module to query."
+        self.__complain("module can't be set")
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "cat " + self.filename
+
+if __name__ == '__main__':
+    if sys.hexversion < 0x02060000:
+        sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+        sys.exit(1)
+    (options, arguments) = getopt.getopt(sys.argv[1:], "vbe:d:C:r:o:ikus:p:z:P:S:aL:A:Rh")
+    verbose = 0
+    bare = False
+    root = None
+    outdir = os.getcwd()
+    remotize = False
+    import_only = False
+    underscore_to_dot = False
+    slashsubst = None
+    authormap = None
+    revisionmap = False
+    backend = cvsps()
+    for (opt, val) in options:
+        if opt == '-v':
+            verbose += 1
+        elif opt == '-b':
+            bare = True
+        elif opt == '-e':
+            for cls in (cvsps, cvs2git):
+                if cls.__name__ == val:
+                    backend = cls()
+                    break
+            else:
+                sys.stderr.write("git cvsimport: unknown engine %s.\n" % val)
+                sys.exit(1)
+        elif opt == '-d':
+            backend.set_repo(val)
+        elif opt == '-C':
+            outdir = val
+        elif opt == '-r':
+            remotize = True
+        elif opt == '-o':
+            sys.stderr.write("git cvsimport: -o is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-i':
+            import_only = True
+        elif opt == '-k':
+            backend.set_nokeywords()
+        elif opt == '-u':
+            underscore_to_dot = True
+        elif opt == '-s':
+            slashsubst = val
+        elif opt == '-p':
+            backend.add_opts(val.replace(",", " "))
+        elif opt == '-z':
+            backend.set_fuzz(val)
+        elif opt == '-P':
+            backend = filesource(val)
+            sys.exit(1)
+        elif opt in ('-m', '-M'):
+            sys.stderr.write("git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\n")
+            sys.exit(1)
+        elif opt == '-S':
+            backend.set_exclusion(val)
+        elif opt == '-a':
+            sys.stderr.write("git cvsimport: -a is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-L':
+            sys.stderr.write("git cvsimport: -L is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-A':
+            authormap = os.path.abspath(val)
+        elif opt == '-R':
+            revisionmap = True
+        else:
+            print """\
+git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>]
+     [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>]
+     [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>]
+     [<CVS_module>]
+"""
+    def metadata(fn, outdir='.'):
+        if bare:
+            return os.path.join(outdir, fn)
+        else:
+            return os.path.join(outdir, ".git", fn)
+
+    try:
+        if outdir:
+            try:
+                # If the output directory does not exist, create it
+                # and initialize it as a git repository.
+                os.mkdir(outdir)
+                do_or_die("git init --quiet " + outdir)
+            except OSError:
+                # Otherwise, assume user wants incremental import.
+                if not bare and not os.path.exists(os.path.join(outdir, ".git")):
+                    raise Fatal("output directory is not a git repository")
+                threshold = capture_or_die("git log -1 --format=%ct").strip()
+                backend.set_after(threshold)
+        if revisionmap:
+            backend.set_revmap(tempfile.mkstemp()[1])
+            markmap = tempfile.mkstemp()[1]
+        if arguments:
+            backend.set_module(arguments[0])
+        gitopts = ""
+        if bare:
+            gitopts += " --bare"
+        if revisionmap:
+            gitopts += " --export-marks='%s'" % markmap
+        if authormap:
+            shutil.copyfile(authormap, metadata("cvs-authors", outdir))
+        if os.path.exists(metadata("cvs-authors", outdir)):
+            backend.set_authormap(metadata("cvs-authors", outdir))
+        do_or_die("%s | (cd %s >/dev/null; git fast-import --quiet %s)" \
+                  % (backend.command(), outdir, gitopts))
+        os.chdir(outdir)
+        if underscore_to_dot or slashsubst:
+            tagnames = capture_or_die("git tag -l")
+            for tag in tagnames.split():
+                if tag:
+                    changed = tag
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if changed != tag:
+                        do_or_die("git tag -f %s %s >/dev/null" % (tag, changed))
+        if underscore_to_dot or slashsubst or remotize:
+            branchnames = capture_or_die("git branch -l")
+            for branch in branchnames.split():
+                if branch:
+                    # Ugh - fragile dependency on branch -l output format
+                    branch = branch[2:]
+                    changed = branch
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if remotize:
+                        changed = os.path.join("remotes", remotize, branch)
+                    if changed != branch:
+                        do_or_die("branch --m %s %s >/dev/null" % (branch, changed))
+        if revisionmap:
+            refd = {}
+            for line in open(backend.revmap):
+                if line.startswith("#"):
+                    continue
+                (fn, rev, mark) = line.split()
+                refd[(fn, rev)] = mark
+            markd = {}
+            for line in open(markmap):
+                if line.startswith("#"):
+                    continue
+                (mark, hashd) = line.split()
+                markd[mark] = hashd
+            with open(metadata("cvs-revisions"), "a") as wfp:
+                for ((fn, rev), val) in refd.items():
+                    if val in markd:
+                        wfp.write("%s %s %s\n" % (fn, rev, markd[val]))
+            os.remove(markmap)
+            os.remove(backend.revmap)
+        if not import_only and not bare:
+            do_or_die("git checkout -q")
+    except Fatal, err:
+        sys.stderr.write("git_cvsimport: " + err.msg + "\n")
+        sys.exit(1)
+    except KeyboardInterrupt:
+        pass
+
+# end
diff --git a/git-cvsimport.sh b/git-cvsimport.sh
index 71ba11d..52ce112 100755
--- a/git-cvsimport.sh
+++ b/git-cvsimport.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
 
-GIT_CVSPS_VERSION=2
+: ${GIT_CVSPS_VERSION=2}
 
-exec git cvsimport-$GIT_CVSPS_VERSION "$@"
+exec git "cvsimport-$GIT_CVSPS_VERSION" "$@"
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* Re: [PATCH 2/3] cvsimport: introduce a version-switch wrapper
  2013-01-14  1:40         ` [PATCH 2/3] cvsimport: introduce a version-switch wrapper Junio C Hamano
@ 2013-01-14  1:47           ` Junio C Hamano
  0 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  1:47 UTC (permalink / raw)
  To: git; +Cc: jrnieder, mhagger, esr, chris

Junio C Hamano <gitster@pobox.com> writes:

> In preparation to have both old and new cvsimport during the
> transition period, rename the cvsimport script to cvsimport-2
> and introduce a small wrapper that we can later change it to
> allow users to run either frontend (with their corresponding
> cvsps backends).
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  .gitignore           |    1 +
>  Makefile             |    3 +-
>  git-cvsimport-2.perl | 1179 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  git-cvsimport.perl   | 1179 --------------------------------------------------

Sorry, I should have done "format-patch -M" for this one.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs
  2013-01-13 22:20     ` Junio C Hamano
  2013-01-13 23:27       ` Junio C Hamano
  2013-01-14  1:40       ` [PATCH 0/3] A smoother transition plan for cvsimport Junio C Hamano
@ 2013-01-14  5:12       ` Michael Haggerty
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
  3 siblings, 0 replies; 37+ messages in thread
From: Michael Haggerty @ 2013-01-14  5:12 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jonathan Nieder, Eric S. Raymond, git, Chris Rorvick

On 01/13/2013 11:20 PM, Junio C Hamano wrote:
> After a quick survey of various distros, I think it is very unlikely
> that we will see "distros move on to newer cvsps, leaving cvsimport
> broken" situation. If anything, it is more like "distros decide to
> ignore the new cvsps, until it is made to work with cvsimport" [*1*].

A better predictor of the distros' decisions is probably which other
packages depend on cvsps.  As one data point: on Debian squeeze and on
Ubuntu precise, only two packages depend on cvsps (git-cvs and
bzr-cvsps-import) and one suggests it (chora2, "a code repository
viewing component for horde framework").  So also by this standard they
are unlikely to feel a lot of pressure to update quickly to cvsps3.

> I think it is probably sensible to [...]
> 
> Agreed?

Yes, I agree that what you propose is a good strategy.

Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v2 0/6] A smoother transition plan for cvsimport
  2013-01-13 22:20     ` Junio C Hamano
                         ` (2 preceding siblings ...)
  2013-01-14  5:12       ` [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Michael Haggerty
@ 2013-01-14  7:25       ` Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 1/6] Makefile: add description on PERL/PYTHON_PATH Junio C Hamano
                           ` (8 more replies)
  3 siblings, 9 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:25 UTC (permalink / raw)
  To: git

The theme is the same as the previous 3-patch series ($gmane/213415).

The first one is unrelated.

The last two are new, adding the "dual-use test" framework hinted
at the end of the previous round.

Oh, also this time the series is formatted with "format-patch -M",
to avoid overwhelming the readers with the rename.

Junio C Hamano (6):
  Makefile: add description on PERL/PYTHON_PATH
  cvsimport: allow setting a custom cvsps (2.x) program name
  cvsimport: introduce a version-switch wrapper
  cvsimport: start adding cvsps 3.x support
  cvsimport: make tests reusable for cvsimport-3
  cvsimport-3: add a sample test

 .gitignore                                 |   1 +
 Makefile                                   |  43 +++-
 git-cvsimport.perl => git-cvsimport-2.perl |   4 +-
 git-cvsimport-3.py                         | 344 +++++++++++++++++++++++++++++
 git-cvsimport.sh                           |   5 +
 t/lib-cvs.sh                               |  55 ++++-
 t/t9650-cvsimport3.sh                      |   4 +
 7 files changed, 440 insertions(+), 16 deletions(-)
 rename git-cvsimport.perl => git-cvsimport-2.perl (99%)
 create mode 100755 git-cvsimport-3.py
 create mode 100755 git-cvsimport.sh
 create mode 100755 t/t9650-cvsimport3.sh

-- 
1.8.1.421.g6236851

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v2 1/6] Makefile: add description on PERL/PYTHON_PATH
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
@ 2013-01-14  7:25         ` Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 2/6] cvsimport: allow setting a custom cvsps (2.x) program name Junio C Hamano
                           ` (7 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:25 UTC (permalink / raw)
  To: git

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Makefile b/Makefile
index 1b30d7b..1695075 100644
--- a/Makefile
+++ b/Makefile
@@ -241,11 +241,16 @@ all::
 # apostrophes to be ASCII so that cut&pasting examples to the shell
 # will work.
 #
+# Define PERL_PATH to the path of your Perl binary (usually /usr/bin/perl).
+#
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
 # Define NO_PERL if you do not want Perl scripts or libraries at all.
 #
+# Define PYTHON_PATH to the path of your Python binary (often /usr/bin/python
+# but /usr/bin/python2.7 on some platforms).
+#
 # Define NO_PYTHON if you do not want Python scripts or libraries at all.
 #
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH v2 2/6] cvsimport: allow setting a custom cvsps (2.x) program name
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 1/6] Makefile: add description on PERL/PYTHON_PATH Junio C Hamano
@ 2013-01-14  7:25         ` Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 3/6] cvsimport: introduce a version-switch wrapper Junio C Hamano
                           ` (6 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:25 UTC (permalink / raw)
  To: git

Distros may ship old cvsps under a different name, or the user may
install it outside the normal $PATH.  Allow setting CVSPS2_PATH from
the build environment.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile           | 9 +++++++--
 git-cvsimport.perl | 4 +++-
 t/lib-cvs.sh       | 4 +++-
 3 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/Makefile b/Makefile
index 1695075..dd2a024 100644
--- a/Makefile
+++ b/Makefile
@@ -576,9 +576,11 @@ endif
 ifndef PYTHON_PATH
 	PYTHON_PATH = /usr/bin/python
 endif
+ifndef CVSPS2_PATH
+	CVSPS2_PATH = cvsps
+endif
 
-export PERL_PATH
-export PYTHON_PATH
+export PERL_PATH PYTHON_PATH CVSPS2_PATH
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
@@ -1516,6 +1518,7 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+CVSPS2_PATH_SQ = $(subst ','\'',$(CVSPS2_PATH))
 DIFF_SQ = $(subst ','\'',$(DIFF))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1729,6 +1732,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl GIT-VERSION-FILE
 	    -e '	H' \
 	    -e '	x' \
 	    -e '}' \
+	    -e 's|@@CVSPS2_PATH@@|$(CVSPS2_PATH_SQ)|g' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
 	    $@.perl >$@+ && \
 	chmod +x $@+ && \
@@ -2107,6 +2111,7 @@ GIT-LDFLAGS: FORCE
 GIT-BUILD-OPTIONS: FORCE
 	@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
 	@echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
+	@echo CVSPS2_PATH=\''$(subst ','\'',$(CVSPS2_PATH_SQ))'\' >>$@
 	@echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@
 	@echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
 	@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 0a31ebd..ad460a5 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -33,6 +33,8 @@
 our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R);
 my (%conv_author_name, %conv_author_email, %conv_author_tz);
 
+my $cvsps2 = "@@CVSPS2_PATH@@";
+
 sub usage(;$) {
 	my $msg = shift;
 	print(STDERR "Error: $msg\n") if $msg;
@@ -751,7 +753,7 @@ sub munge_user_filename {
 		unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
 			push @opt, '--cvs-direct';
 		}
-		exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+		exec($cvsps2,"--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
 		die "Could not start cvsps: $!\n";
 	}
 	($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index 44263ad..bdab63c 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -13,7 +13,9 @@ fi
 CVS="cvs -f"
 export CVS
 
-cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
+CVSPS="$CVSPS2_PATH"
+
+cvsps_version=`$CVSPS -h 2>&1 | sed -ne 's/cvsps version //p'`
 case "$cvsps_version" in
 2.1 | 2.2*)
 	;;
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH v2 3/6] cvsimport: introduce a version-switch wrapper
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 1/6] Makefile: add description on PERL/PYTHON_PATH Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 2/6] cvsimport: allow setting a custom cvsps (2.x) program name Junio C Hamano
@ 2013-01-14  7:25         ` Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 4/6] cvsimport: start adding cvsps 3.x support Junio C Hamano
                           ` (5 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:25 UTC (permalink / raw)
  To: git

In preparation to have both old and new cvsimport during the
transition period, rename the cvsimport script to cvsimport-2
and introduce a small wrapper that we can later change it to
allow users to run either frontend (with their corresponding
cvsps backends).

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 .gitignore                                 | 1 +
 Makefile                                   | 9 ++++++++-
 git-cvsimport.perl => git-cvsimport-2.perl | 0
 git-cvsimport.sh                           | 5 +++++
 4 files changed, 14 insertions(+), 1 deletion(-)
 rename git-cvsimport.perl => git-cvsimport-2.perl (100%)
 create mode 100755 git-cvsimport.sh

diff --git a/.gitignore b/.gitignore
index aa258a6..8cb799c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@
 /git-credential-store
 /git-cvsexportcommit
 /git-cvsimport
+/git-cvsimport-2
 /git-cvsserver
 /git-daemon
 /git-diff
diff --git a/Makefile b/Makefile
index dd2a024..f3113a9 100644
--- a/Makefile
+++ b/Makefile
@@ -253,6 +253,9 @@ all::
 #
 # Define NO_PYTHON if you do not want Python scripts or libraries at all.
 #
+# Define CVSPS2_PATH if you cannot invoke cvsps (version 2.x) as "cvsps"
+# using your $PATH; if you do not have any, define CVSPS2_PATH=NoThanks.
+#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -440,6 +443,7 @@ unexport CDPATH
 
 SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
+SCRIPT_SH += git-cvsimport.sh
 SCRIPT_SH += git-difftool--helper.sh
 SCRIPT_SH += git-filter-branch.sh
 SCRIPT_SH += git-lost-found.sh
@@ -468,12 +472,15 @@ SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
-SCRIPT_PERL += git-cvsimport.perl
+ifneq ($(CVSPS2_PATH),NoThanks)
+SCRIPT_PERL += git-cvsimport-2.perl
+endif
 SCRIPT_PERL += git-cvsserver.perl
 SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
 SCRIPT_PERL += git-svn.perl
 
+SCRIPT_PYTHON += git-p4.py
 SCRIPT_PYTHON += git-remote-testpy.py
 SCRIPT_PYTHON += git-p4.py
 
diff --git a/git-cvsimport.perl b/git-cvsimport-2.perl
similarity index 100%
rename from git-cvsimport.perl
rename to git-cvsimport-2.perl
diff --git a/git-cvsimport.sh b/git-cvsimport.sh
new file mode 100755
index 0000000..71ba11d
--- /dev/null
+++ b/git-cvsimport.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+GIT_CVSPS_VERSION=2
+
+exec git cvsimport-$GIT_CVSPS_VERSION "$@"
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH v2 4/6] cvsimport: start adding cvsps 3.x support
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
                           ` (2 preceding siblings ...)
  2013-01-14  7:25         ` [PATCH v2 3/6] cvsimport: introduce a version-switch wrapper Junio C Hamano
@ 2013-01-14  7:25         ` Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 5/6] cvsimport: make tests reusable for cvsimport-3 Junio C Hamano
                           ` (4 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:25 UTC (permalink / raw)
  To: git

The new cvsps 3.x series lacks support of some options cvsps 2.x
series had and used by cvsimport-2; add a replacement program from
the author of cvsps 3.x and allow users to choose it by setting the
GIT_CVSPS_VERSION environment variable to 3.  We would later add
support to auto-detect the version of installed cvsps to this code
when the environment variable is not set.

Note that in this step, cvsimport-3 that relies on cvsps 3.x does
not have any test coverage.  As cvsimport-3 supports most of the
command line options cvsimport-2, we should be able to tweak some of
the t96?? tests and run them with GIT_CVSPS_VERSION set to both 2
and 3, but that is a topic of later patches that should come on top.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile           |  22 +++-
 git-cvsimport-3.py | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 git-cvsimport.sh   |   4 +-
 3 files changed, 363 insertions(+), 7 deletions(-)
 create mode 100755 git-cvsimport-3.py

diff --git a/Makefile b/Makefile
index f3113a9..00bd3a6 100644
--- a/Makefile
+++ b/Makefile
@@ -256,6 +256,9 @@ all::
 # Define CVSPS2_PATH if you cannot invoke cvsps (version 2.x) as "cvsps"
 # using your $PATH; if you do not have any, define CVSPS2_PATH=NoThanks.
 #
+# Define CVSPS3_PATH if you cannot invoke cvsps (version 3.x) as "cvsps"
+# using your $PATH; if you do not have any, define CVSPS3_PATH=NoThanks.
+#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -480,9 +483,11 @@ SCRIPT_PERL += git-relink.perl
 SCRIPT_PERL += git-send-email.perl
 SCRIPT_PERL += git-svn.perl
 
+ifneq ($(CVSPS3_PATH),NoThanks)
+SCRIPT_PYTHON += git-cvsimport-3.py
+endif
 SCRIPT_PYTHON += git-p4.py
 SCRIPT_PYTHON += git-remote-testpy.py
-SCRIPT_PYTHON += git-p4.py
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
 	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
@@ -586,8 +591,11 @@ endif
 ifndef CVSPS2_PATH
 	CVSPS2_PATH = cvsps
 endif
+ifndef CVSPS3_PATH
+	CVSPS3_PATH = cvsps
+endif
 
-export PERL_PATH PYTHON_PATH CVSPS2_PATH
+export PERL_PATH PYTHON_PATH CVSPS2_PATH CVSPS3_PATH
 
 LIB_FILE = libgit.a
 XDIFF_LIB = xdiff/lib.a
@@ -1526,6 +1534,7 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 CVSPS2_PATH_SQ = $(subst ','\'',$(CVSPS2_PATH))
+CVSPS3_PATH_SQ = $(subst ','\'',$(CVSPS3_PATH))
 DIFF_SQ = $(subst ','\'',$(DIFF))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1768,12 +1777,14 @@ ifndef NO_PYTHON
 $(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
 $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
 	$(QUIET_GEN)$(RM) $@ $@+ && \
-	INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
+	INSTLIBDIR_SQ=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
 		--no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
-		instlibdir` && \
+		instlibdir | \
+		sed -e "s/'/'\''/g"` && \
 	sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
 	    -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
-	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+	    -e 's|@@CVSPS3_PATH@@|$(CVSPS3_PATH_SQ)|g' \
+	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR_SQ"'|g' \
 	    $@.py >$@+ && \
 	chmod +x $@+ && \
 	mv $@+ $@
@@ -2119,6 +2130,7 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
 	@echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
 	@echo CVSPS2_PATH=\''$(subst ','\'',$(CVSPS2_PATH_SQ))'\' >>$@
+	@echo CVSPS3_PATH=\''$(subst ','\'',$(CVSPS3_PATH_SQ))'\' >>$@
 	@echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@
 	@echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
 	@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
diff --git a/git-cvsimport-3.py b/git-cvsimport-3.py
new file mode 100755
index 0000000..57f13b7
--- /dev/null
+++ b/git-cvsimport-3.py
@@ -0,0 +1,344 @@
+#!/usr/bin/env python
+#
+# Import CVS history into git
+#
+# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.
+#
+# By Eric S. Raymond <esr@thyrsus.com>, December 2012
+# May be redistributed under the license of the git project.
+
+import sys
+
+if sys.hexversion < 0x02060000:
+    sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+    sys.exit(1)
+
+import os, getopt, subprocess, tempfile, shutil
+
+DEBUG_COMMANDS = 1
+
+class Fatal(Exception):
+    "Unrecoverable error."
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def do_or_die(dcmd, legend=""):
+    "Either execute a command or raise a fatal exception."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        retcode = subprocess.call(dcmd, shell=True)
+        if retcode < 0:
+            raise Fatal("git cvsimport: child was terminated by signal %d." % -retcode)
+        elif retcode != 0:
+            raise Fatal("git cvsimport: child returned %d." % retcode)
+    except (OSError, IOError) as e:
+        raise Fatal("git cvsimport: execution of %s%s failed: %s" % (dcmd, legend, e))
+
+def capture_or_die(dcmd, legend=""):
+    "Either execute a command and capture its output or die."
+    if legend:
+        legend = " "  + legend
+    if verbose >= DEBUG_COMMANDS:
+        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
+    try:
+        return subprocess.check_output(dcmd, shell=True)
+    except subprocess.CalledProcessError as e:
+        if e.returncode < 0:
+            sys.stderr.write("git cvsimport: child was terminated by signal %d." % -e.returncode)
+        elif e.returncode != 0:
+            sys.stderr.write("git cvsimport: child returned %d." % e.returncode)
+        sys.exit(1)
+
+class cvsps:
+    "Method class for cvsps back end."
+
+    cvsps = "@@CVSPS3_PATH@@"
+    def __init__(self):
+        self.opts = ""
+        self.revmap = None
+    def set_repo(self, val):
+        "Set the repository root option."
+        if not val.startswith(":"):
+            if not val.startswith(os.sep):
+                val = os.path.abspath(val)
+            val = ":local:" + val
+        self.opts += " --root '%s'" % val
+    def set_authormap(self, val):
+        "Set the author-map file."
+        self.opts += " -A '%s'" % val
+    def set_fuzz(self, val):
+        "Set the commit-similarity window."
+        self.opts += " -z %s" % val
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " -k"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " -n -f '%s'" % val
+    def set_after(self, val):
+        "Set a date threshold for incremental import."
+        self.opts += " -d '%s'" % val
+    def set_revmap(self, val):
+        "Set the file to which the engine should dump a reference map."
+        self.revmap = val
+        self.opts += " -R '%s'" % self.revmap
+    def set_module(self, val):
+        "Set the module to query."
+        self.opts += " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return self.cvsps + "--fast-export " + self.opts
+
+class cvs2git:
+    "Method class for cvs2git back end."
+    def __init__(self):
+        self.opts = ""
+        self.modulepath = "."
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author maping is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        sys.stderr.write("git cvsimport: cvs2git must run within a repository checkout directory.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        sys.stderr.write("git cvsimport: fuzz setting is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_nokeywords(self):
+        "Suppress CVS keyword expansion."
+        self.opts += " --keywords-off"
+    def add_opts(self, val):
+        "Add options to the engine command line."
+        self.opts += " " + val
+    def set_exclusion(self, val):
+        "Set a file exclusion regexp."
+        self.opts += " --exclude='%s'" % val
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        sys.stderr.write("git cvsimport: incremental import is not supported with cvs2git.\n")
+        sys.exit(1)
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, val):
+        "Set the module to query."
+        self.modulepath = " " + val
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "(cvs2git --username=git-cvsimport --quiet --quiet --blobfile={0} --dumpfile={1} {2} {3} && cat {0} {1} && rm {0} {1})".format(tempfile.mkstemp()[1], tempfile.mkstemp()[1], self.opts, self.modulepath)
+
+class filesource:
+    "Method class for file-source back end."
+    def __init__(self, filename):
+        self.filename = filename
+    def __complain(self, legend):
+        sys.stderr.write("git cvsimport: %s with file source.\n" % legend)
+        sys.exit(1)
+    def set_repo(self, _val):
+        "Set the repository root option."
+        self.__complain("repository can't be set")
+    def set_authormap(self, _val):
+        "Set the author-map file."
+        sys.stderr.write("git cvsimport: author mapping is not supported with filesource.\n")
+        sys.exit(1)
+    def set_fuzz(self, _val):
+        "Set the commit-similarity window."
+        self.__complain("fuzz can't be set")
+    def set_nokeywords(self, _val):
+        "Suppress CVS keyword expansion."
+        self.__complain("keyword suppression can't be set")
+    def add_opts(self, _val):
+        "Add options to the engine command line."
+        self.__complain("other options can't be set")
+    def set_exclusion(self, _val):
+        "Set a file exclusion regexp."
+        self.__complain("exclusions can't be set")
+    def set_after(self, _val):
+        "Set a date threshold for incremental import."
+        pass
+    def set_revmap(self, _val):
+        "Set the file to which the engine should dump a reference map."
+        sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n")
+        sys.exit(1)
+    def set_module(self, _val):
+        "Set the module to query."
+        self.__complain("module can't be set")
+    def command(self):
+        "Emit the command implied by all previous options."
+        return "cat " + self.filename
+
+if __name__ == '__main__':
+    if sys.hexversion < 0x02060000:
+        sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
+        sys.exit(1)
+    (options, arguments) = getopt.getopt(sys.argv[1:], "vbe:d:C:r:o:ikus:p:z:P:S:aL:A:Rh")
+    verbose = 0
+    bare = False
+    root = None
+    outdir = os.getcwd()
+    remotize = False
+    import_only = False
+    underscore_to_dot = False
+    slashsubst = None
+    authormap = None
+    revisionmap = False
+    backend = cvsps()
+    for (opt, val) in options:
+        if opt == '-v':
+            verbose += 1
+        elif opt == '-b':
+            bare = True
+        elif opt == '-e':
+            for cls in (cvsps, cvs2git):
+                if cls.__name__ == val:
+                    backend = cls()
+                    break
+            else:
+                sys.stderr.write("git cvsimport: unknown engine %s.\n" % val)
+                sys.exit(1)
+        elif opt == '-d':
+            backend.set_repo(val)
+        elif opt == '-C':
+            outdir = val
+        elif opt == '-r':
+            remotize = True
+        elif opt == '-o':
+            sys.stderr.write("git cvsimport: -o is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-i':
+            import_only = True
+        elif opt == '-k':
+            backend.set_nokeywords()
+        elif opt == '-u':
+            underscore_to_dot = True
+        elif opt == '-s':
+            slashsubst = val
+        elif opt == '-p':
+            backend.add_opts(val.replace(",", " "))
+        elif opt == '-z':
+            backend.set_fuzz(val)
+        elif opt == '-P':
+            backend = filesource(val)
+            sys.exit(1)
+        elif opt in ('-m', '-M'):
+            sys.stderr.write("git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\n")
+            sys.exit(1)
+        elif opt == '-S':
+            backend.set_exclusion(val)
+        elif opt == '-a':
+            sys.stderr.write("git cvsimport: -a is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-L':
+            sys.stderr.write("git cvsimport: -L is no longer supported.\n")
+            sys.exit(1)
+        elif opt == '-A':
+            authormap = os.path.abspath(val)
+        elif opt == '-R':
+            revisionmap = True
+        else:
+            print """\
+git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>]
+     [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>]
+     [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>]
+     [<CVS_module>]
+"""
+    def metadata(fn, outdir='.'):
+        if bare:
+            return os.path.join(outdir, fn)
+        else:
+            return os.path.join(outdir, ".git", fn)
+
+    try:
+        if outdir:
+            try:
+                # If the output directory does not exist, create it
+                # and initialize it as a git repository.
+                os.mkdir(outdir)
+                do_or_die("git init --quiet " + outdir)
+            except OSError:
+                # Otherwise, assume user wants incremental import.
+                if not bare and not os.path.exists(os.path.join(outdir, ".git")):
+                    raise Fatal("output directory is not a git repository")
+                threshold = capture_or_die("git log -1 --format=%ct").strip()
+                backend.set_after(threshold)
+        if revisionmap:
+            backend.set_revmap(tempfile.mkstemp()[1])
+            markmap = tempfile.mkstemp()[1]
+        if arguments:
+            backend.set_module(arguments[0])
+        gitopts = ""
+        if bare:
+            gitopts += " --bare"
+        if revisionmap:
+            gitopts += " --export-marks='%s'" % markmap
+        if authormap:
+            shutil.copyfile(authormap, metadata("cvs-authors", outdir))
+        if os.path.exists(metadata("cvs-authors", outdir)):
+            backend.set_authormap(metadata("cvs-authors", outdir))
+        do_or_die("%s | (cd %s >/dev/null; git fast-import --quiet %s)" \
+                  % (backend.command(), outdir, gitopts))
+        os.chdir(outdir)
+        if underscore_to_dot or slashsubst:
+            tagnames = capture_or_die("git tag -l")
+            for tag in tagnames.split():
+                if tag:
+                    changed = tag
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if changed != tag:
+                        do_or_die("git tag -f %s %s >/dev/null" % (tag, changed))
+        if underscore_to_dot or slashsubst or remotize:
+            branchnames = capture_or_die("git branch -l")
+            for branch in branchnames.split():
+                if branch:
+                    # Ugh - fragile dependency on branch -l output format
+                    branch = branch[2:]
+                    changed = branch
+                    if underscore_to_dot:
+                        changed = changed.replace("_", ".")
+                    if slashsubst:
+                        changed = changed.replace(os.sep, slashsubst)
+                    if remotize:
+                        changed = os.path.join("remotes", remotize, branch)
+                    if changed != branch:
+                        do_or_die("branch --m %s %s >/dev/null" % (branch, changed))
+        if revisionmap:
+            refd = {}
+            for line in open(backend.revmap):
+                if line.startswith("#"):
+                    continue
+                (fn, rev, mark) = line.split()
+                refd[(fn, rev)] = mark
+            markd = {}
+            for line in open(markmap):
+                if line.startswith("#"):
+                    continue
+                (mark, hashd) = line.split()
+                markd[mark] = hashd
+            with open(metadata("cvs-revisions"), "a") as wfp:
+                for ((fn, rev), val) in refd.items():
+                    if val in markd:
+                        wfp.write("%s %s %s\n" % (fn, rev, markd[val]))
+            os.remove(markmap)
+            os.remove(backend.revmap)
+        if not import_only and not bare:
+            do_or_die("git checkout -q")
+    except Fatal, err:
+        sys.stderr.write("git_cvsimport: " + err.msg + "\n")
+        sys.exit(1)
+    except KeyboardInterrupt:
+        pass
+
+# end
diff --git a/git-cvsimport.sh b/git-cvsimport.sh
index 71ba11d..52ce112 100755
--- a/git-cvsimport.sh
+++ b/git-cvsimport.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
 
-GIT_CVSPS_VERSION=2
+: ${GIT_CVSPS_VERSION=2}
 
-exec git cvsimport-$GIT_CVSPS_VERSION "$@"
+exec git "cvsimport-$GIT_CVSPS_VERSION" "$@"
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH v2 5/6] cvsimport: make tests reusable for cvsimport-3
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
                           ` (3 preceding siblings ...)
  2013-01-14  7:25         ` [PATCH v2 4/6] cvsimport: start adding cvsps 3.x support Junio C Hamano
@ 2013-01-14  7:25         ` Junio C Hamano
  2013-01-14  7:25         ` [PATCH v2 6/6] cvsimport-3: add a sample test Junio C Hamano
                           ` (3 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:25 UTC (permalink / raw)
  To: git

This way, a dual-use test can be dot-sourced into a new test script
after defining GIT_CVSPS_VERSION=3 to test that the new cvsimport
with cvsps (3.x) works on simple test histories that old cvsimport
can grok correctly.

Also allow CVSPS2_PATH and CVSPS3_PATH to be defined as "NoThanks"
to cause the tests in t96?? series to be skipped.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/lib-cvs.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 47 insertions(+), 10 deletions(-)

diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index bdab63c..3a55b8a 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -1,5 +1,8 @@
 #!/bin/sh
 
+: ${TEST_CVSPS_VERSION=2}
+export TEST_CVSPS_VERSION
+
 . ./test-lib.sh
 
 unset CVS_SERVER
@@ -13,22 +16,56 @@ fi
 CVS="cvs -f"
 export CVS
 
-CVSPS="$CVSPS2_PATH"
-
-cvsps_version=`$CVSPS -h 2>&1 | sed -ne 's/cvsps version //p'`
-case "$cvsps_version" in
-2.1 | 2.2*)
+skip_all=
+case "$TEST_CVSPS_VERSION" in
+2)
+	if test "$CVSPS2_PATH" = NoThanks
+	then
+		skip_all='skipping cvsimport tests, cvsps(2) not used'
+	else
+		case $($CVSPS2_PATH -h 2>&1 | sed -ne 's/cvsps version //p') in
+		2.1 | 2.2*)
+			;;
+		'')
+			skip_all='skipping cvsimport tests, cvsps(2) not found'
+			;;
+		*)
+			skip_all='skipping cvsimport tests, unsupported cvsps(2)'
+			;;
+		esac
+	fi
 	;;
-'')
-	skip_all='skipping cvsimport tests, cvsps not found'
-	test_done
+3)
+	if test "$CVSPS3_PATH" = NoThanks
+	then
+		skip_all='skipping cvsimport tests, cvsps(3) not used'
+	else
+		case $($CVSPS3_PATH -h 2>&1 | sed -ne 's/cvsps version //p') in
+		3.*)
+			;;
+		'')
+			skip_all='skipping cvsimport tests, cvsps(3) not found'
+			;;
+		*)
+			skip_all='skipping cvsimport tests, unsupported cvsps(3)'
+			;;
+		esac
+	fi
 	;;
 *)
-	skip_all='skipping cvsimport tests, unsupported cvsps version'
-	test_done
+	echo >&2 "Bug in test: set TEST_CVSPS_VESION to either 2 or 3"
+	exit 1
 	;;
 esac
 
+GIT_CVSPS_VERSION=$TEST_CVSPS_VERSION
+export GIT_CVSPS_VERSION
+
+if test -n "$skip_all"
+then
+	test_done
+fi
+
 setup_cvs_test_repository () {
 	CVSROOT="$(pwd)/.cvsroot" &&
 	cp -r "$TEST_DIRECTORY/$1/cvsroot" "$CVSROOT" &&
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH v2 6/6] cvsimport-3: add a sample test
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
                           ` (4 preceding siblings ...)
  2013-01-14  7:25         ` [PATCH v2 5/6] cvsimport: make tests reusable for cvsimport-3 Junio C Hamano
@ 2013-01-14  7:25         ` Junio C Hamano
  2013-01-14  7:47         ` [PATCH v2 0/6] A smoother transition plan for cvsimport Jonathan Nieder
                           ` (2 subsequent siblings)
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:25 UTC (permalink / raw)
  To: git

This is primarily for illustration to show how the support
introduced by the previous step can be used.

Some parts of t9600 needs to be made conditional to the value of
TEST_CVSPS_VERSION to avoid passing options the new cvsimport does
not need or understand, before this can become usable as a real
test.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t9650-cvsimport3.sh | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100755 t/t9650-cvsimport3.sh

diff --git a/t/t9650-cvsimport3.sh b/t/t9650-cvsimport3.sh
new file mode 100755
index 0000000..f7ca986
--- /dev/null
+++ b/t/t9650-cvsimport3.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+TEST_CVSPS_VERSION=3
+. ./t9600-cvsimport.sh
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* Re: [PATCH v2 0/6] A smoother transition plan for cvsimport
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
                           ` (5 preceding siblings ...)
  2013-01-14  7:25         ` [PATCH v2 6/6] cvsimport-3: add a sample test Junio C Hamano
@ 2013-01-14  7:47         ` Jonathan Nieder
  2013-01-14  7:48         ` [PATCH v2 7/6] t9600: further prepare for sharing Junio C Hamano
  2013-01-14  7:52         ` [PATCH v2 8/6] t9600: adjust for new cvsimport Junio C Hamano
  8 siblings, 0 replies; 37+ messages in thread
From: Jonathan Nieder @ 2013-01-14  7:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano wrote:

> Junio C Hamano (6):
>   Makefile: add description on PERL/PYTHON_PATH
>   cvsimport: allow setting a custom cvsps (2.x) program name
>   cvsimport: introduce a version-switch wrapper
>   cvsimport: start adding cvsps 3.x support
>   cvsimport: make tests reusable for cvsimport-3
>   cvsimport-3: add a sample test

Looks very sane.

^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v2 7/6] t9600: further prepare for sharing
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
                           ` (6 preceding siblings ...)
  2013-01-14  7:47         ` [PATCH v2 0/6] A smoother transition plan for cvsimport Jonathan Nieder
@ 2013-01-14  7:48         ` Junio C Hamano
  2013-01-14  7:52         ` [PATCH v2 8/6] t9600: adjust for new cvsimport Junio C Hamano
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:48 UTC (permalink / raw)
  To: git

When t9650, which dot-sources this script, really becomes ready to
test the new cvsimport, we no longer depend on having PERL to run
this test through it.  Move the PERL dependency to lib-cvs.sh
instead.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/lib-cvs.sh         |  7 +++++++
 t/t9600-cvsimport.sh | 30 +++++++++++++++---------------
 2 files changed, 22 insertions(+), 15 deletions(-)

diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh
index 3a55b8a..d2bf5bf 100644
--- a/t/lib-cvs.sh
+++ b/t/lib-cvs.sh
@@ -22,6 +22,9 @@ case "$TEST_CVSPS_VERSION" in
 	if test "$CVSPS2_PATH" = NoThanks
 	then
 		skip_all='skipping cvsimport tests, cvsps(2) not used'
+	elif ! test_have_prereq PERL
+	then
+		skip_all='skipping cvsimport tests, Perl not available'
 	else
 		case $($CVSPS2_PATH -h 2>&1 | sed -ne 's/cvsps version //p') in
 		2.1 | 2.2*)
@@ -39,6 +42,10 @@ case "$TEST_CVSPS_VERSION" in
 	if test "$CVSPS3_PATH" = NoThanks
 	then
 		skip_all='skipping cvsimport tests, cvsps(3) not used'
+	elif ! test_have_prereq PYTHON
+	then
+		# NEEDSWORK: we may need Python lower-bound prerequisite
+		skip_all='skipping cvsimport tests, Python not available'
 	else
 		case $($CVSPS3_PATH -h 2>&1 | sed -ne 's/cvsps version //p') in
 		3.*)
diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh
index 4c384ff..906cbdc 100755
--- a/t/t9600-cvsimport.sh
+++ b/t/t9600-cvsimport.sh
@@ -3,14 +3,14 @@
 test_description='git cvsimport basic tests'
 . ./lib-cvs.sh
 
-test_expect_success PERL 'setup cvsroot environment' '
+test_expect_success 'setup cvsroot environment' '
 	CVSROOT=$(pwd)/cvsroot &&
 	export CVSROOT
 '
 
-test_expect_success PERL 'setup cvsroot' '$CVS init'
+test_expect_success 'setup cvsroot' '$CVS init'
 
-test_expect_success PERL 'setup a cvs module' '
+test_expect_success 'setup a cvs module' '
 
 	mkdir "$CVSROOT/module" &&
 	$CVS co -d module-cvs module &&
@@ -42,23 +42,23 @@ EOF
 	)
 '
 
-test_expect_success PERL 'import a trivial module' '
+test_expect_success 'import a trivial module' '
 
 	git cvsimport -a -R -z 0 -C module-git module &&
 	test_cmp module-cvs/o_fortuna module-git/o_fortuna
 
 '
 
-test_expect_success PERL 'pack refs' '(cd module-git && git gc)'
+test_expect_success 'pack refs' '(cd module-git && git gc)'
 
-test_expect_success PERL 'initial import has correct .git/cvs-revisions' '
+test_expect_success 'initial import has correct .git/cvs-revisions' '
 
 	(cd module-git &&
 	 git log --format="o_fortuna 1.1 %H" -1) > expected &&
 	test_cmp expected module-git/.git/cvs-revisions
 '
 
-test_expect_success PERL 'update cvs module' '
+test_expect_success 'update cvs module' '
 	(cd module-cvs &&
 	cat <<EOF >o_fortuna &&
 O Fortune,
@@ -86,7 +86,7 @@ EOF
 	)
 '
 
-test_expect_success PERL 'update git module' '
+test_expect_success 'update git module' '
 
 	(cd module-git &&
 	git config cvsimport.trackRevisions true &&
@@ -97,7 +97,7 @@ test_expect_success PERL 'update git module' '
 
 '
 
-test_expect_success PERL 'update has correct .git/cvs-revisions' '
+test_expect_success 'update has correct .git/cvs-revisions' '
 
 	(cd module-git &&
 	 git log --format="o_fortuna 1.1 %H" -1 HEAD^ &&
@@ -105,7 +105,7 @@ test_expect_success PERL 'update has correct .git/cvs-revisions' '
 	test_cmp expected module-git/.git/cvs-revisions
 '
 
-test_expect_success PERL 'update cvs module' '
+test_expect_success 'update cvs module' '
 
 	(cd module-cvs &&
 		echo 1 >tick &&
@@ -114,7 +114,7 @@ test_expect_success PERL 'update cvs module' '
 	)
 '
 
-test_expect_success PERL 'cvsimport.module config works' '
+test_expect_success 'cvsimport.module config works' '
 
 	(cd module-git &&
 		git config cvsimport.module module &&
@@ -126,7 +126,7 @@ test_expect_success PERL 'cvsimport.module config works' '
 
 '
 
-test_expect_success PERL 'second update has correct .git/cvs-revisions' '
+test_expect_success 'second update has correct .git/cvs-revisions' '
 
 	(cd module-git &&
 	 git log --format="o_fortuna 1.1 %H" -1 HEAD^^ &&
@@ -135,7 +135,7 @@ test_expect_success PERL 'second update has correct .git/cvs-revisions' '
 	test_cmp expected module-git/.git/cvs-revisions
 '
 
-test_expect_success PERL 'import from a CVS working tree' '
+test_expect_success 'import from a CVS working tree' '
 
 	$CVS co -d import-from-wt module &&
 	(cd import-from-wt &&
@@ -148,12 +148,12 @@ test_expect_success PERL 'import from a CVS working tree' '
 
 '
 
-test_expect_success PERL 'no .git/cvs-revisions created by default' '
+test_expect_success 'no .git/cvs-revisions created by default' '
 
 	! test -e import-from-wt/.git/cvs-revisions
 
 '
 
-test_expect_success PERL 'test entire HEAD' 'test_cmp_branch_tree master'
+test_expect_success 'test entire HEAD' 'test_cmp_branch_tree master'
 
 test_done
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* [PATCH v2 8/6] t9600: adjust for new cvsimport
  2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
                           ` (7 preceding siblings ...)
  2013-01-14  7:48         ` [PATCH v2 7/6] t9600: further prepare for sharing Junio C Hamano
@ 2013-01-14  7:52         ` Junio C Hamano
  8 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-14  7:52 UTC (permalink / raw)
  To: git

The rewritten cvsimport does not skip the latest 10 minutes worth of
CVS commits by default, so there is no need to pass the "-a" option;
it will barf if it sees "-a".

Also it will do the merge itself, so there is no need to merge
"origin" ourselves, either.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * This is still untested and primarily to illustrate the concept
   introduced by the 5/6 patch, together with 6/6.  Testing by
   interested parties with a working cvsps 3 may be nice.  Something
   like:

    $ make CVSPS3_PATH=/path/to/your/cvsps3/bin/cvsps
    $ cd t && sh t9650-cvsimport3.sh

 t/t9600-cvsimport.sh | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh
index 906cbdc..7b5a9a0 100755
--- a/t/t9600-cvsimport.sh
+++ b/t/t9600-cvsimport.sh
@@ -42,9 +42,13 @@ EOF
 	)
 '
 
-test_expect_success 'import a trivial module' '
+case "$TEST_CVSPS_VERSION" in
+3)	import_all= ;;
+*)	import_all=-a ;;
+esac &&
 
-	git cvsimport -a -R -z 0 -C module-git module &&
+test_expect_success 'import a trivial module' '
+	git cvsimport $import_all -R -z 0 -C module-git module &&
 	test_cmp module-cvs/o_fortuna module-git/o_fortuna
 
 '
@@ -90,8 +94,11 @@ test_expect_success 'update git module' '
 
 	(cd module-git &&
 	git config cvsimport.trackRevisions true &&
-	git cvsimport -a -z 0 module &&
-	git merge origin
+	git cvsimport $import_all -z 0 module &&
+	if test "$TEST_CVSPS_VERSION" = 2
+	then
+		git merge origin
+	fi
 	) &&
 	test_cmp module-cvs/o_fortuna module-git/o_fortuna
 
@@ -119,8 +126,11 @@ test_expect_success 'cvsimport.module config works' '
 	(cd module-git &&
 		git config cvsimport.module module &&
 		git config cvsimport.trackRevisions true &&
-		git cvsimport -a -z0 &&
-		git merge origin
+		git cvsimport $import_all -z0 &&
+		if test "$TEST_CVSPS_VERSION" = 2
+		then
+			git merge origin
+		fi
 	) &&
 	test_cmp module-cvs/tick module-git/tick
 
@@ -140,7 +150,7 @@ test_expect_success 'import from a CVS working tree' '
 	$CVS co -d import-from-wt module &&
 	(cd import-from-wt &&
 		git config cvsimport.trackRevisions false &&
-		git cvsimport -a -z0 &&
+		git cvsimport $import_all -z0 &&
 		echo 1 >expect &&
 		git log -1 --pretty=format:%s%n >actual &&
 		test_cmp actual expect
-- 
1.8.1.421.g6236851

^ permalink raw reply related	[flat|nested] 37+ messages in thread

* Re: [PATCH 3/3] cvsimport: start adding cvsps 3.x support
  2013-01-14  1:40         ` [PATCH 3/3] cvsimport: start adding cvsps 3.x support Junio C Hamano
@ 2013-01-15  6:19           ` Chris Rorvick
  2013-01-15  6:44             ` Junio C Hamano
  0 siblings, 1 reply; 37+ messages in thread
From: Chris Rorvick @ 2013-01-15  6:19 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, jrnieder, mhagger, esr

On Sun, Jan 13, 2013 at 7:40 PM, Junio C Hamano <gitster@pobox.com> wrote:
> The new cvsps 3.x series lacks support of some options cvsps 2.x
> series had and used by cvsimport-2; add a replacement program from
> the author of cvsps 3.x and allow users to choose it by setting the
> GIT_CVSPS_VERSION environment variable to 3.  We would later add
> support to auto-detect the version of installed cvsps to this code
> when the environment variable is not set.
>
> Note that in this step, cvsimport-3 that relies on cvsps 3.x does
> not have any test coverage.  As cvsimport-3 supports most of the
> command line options cvsimport-2, we should be able to tweak some of
> t96xx tests and run them with GIT_CVSPS_VERSION set to both 2 and 3,
> but that is a topic of later patches that should come on top.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  Makefile           |  18 ++-
>  git-cvsimport-3.py | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  git-cvsimport.sh   |   4 +-
>  3 files changed, 359 insertions(+), 7 deletions(-)
>  create mode 100755 git-cvsimport-3.py
>
> diff --git a/Makefile b/Makefile
> index b022db2..060cdc2 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -470,8 +470,9 @@ SCRIPT_PERL += git-relink.perl
>  SCRIPT_PERL += git-send-email.perl
>  SCRIPT_PERL += git-svn.perl
>
> -SCRIPT_PYTHON += git-remote-testpy.py
> +SCRIPT_PYTHON += git-cvsimport-3.py
>  SCRIPT_PYTHON += git-p4.py
> +SCRIPT_PYTHON += git-remote-testpy.py
>
>  SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
>           $(patsubst %.perl,%,$(SCRIPT_PERL)) \
> @@ -575,8 +576,11 @@ endif
>  ifndef CVSPS2_PATH
>         CVSPS2_PATH = cvsps
>  endif
> +ifndef CVSPS3_PATH
> +       CVSPS3_PATH = cvsps
> +endif
>
> -export PERL_PATH PYTHON_PATH CVSPS2_PATH
> +export PERL_PATH PYTHON_PATH CVSPS2_PATH CVSPS3_PATH
>
>  LIB_FILE = libgit.a
>  XDIFF_LIB = xdiff/lib.a
> @@ -1515,6 +1519,7 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
>  PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
>  TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
>  CVSPS2_PATH_SQ = $(subst ','\'',$(CVSPS2_PATH))
> +CVSPS3_PATH_SQ = $(subst ','\'',$(CVSPS3_PATH))
>  DIFF_SQ = $(subst ','\'',$(DIFF))
>
>  LIBS = $(GITLIBS) $(EXTLIBS)
> @@ -1757,12 +1762,15 @@ ifndef NO_PYTHON
>  $(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
>  $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
>         $(QUIET_GEN)$(RM) $@ $@+ && \
> -       INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
> +       INSTLIBDIR_SQ=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
>                 --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
> -               instlibdir` && \
> +               instlibdir | \
> +               sed -e "s/'/'\''/g"` && \
> +       echo "InstLibDir is <$$INSTLIBDIR_SQ>" && \
>         sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
>             -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
> -           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
> +           -e 's|@@CVSPS3_PATH@@|$(CVSPS3_PATH_SQ)|g' \
> +           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR_SQ"'|g' \
>             $@.py >$@+ && \
>         chmod +x $@+ && \
>         mv $@+ $@
> diff --git a/git-cvsimport-3.py b/git-cvsimport-3.py
> new file mode 100755
> index 0000000..57f13b7
> --- /dev/null
> +++ b/git-cvsimport-3.py
> @@ -0,0 +1,344 @@
> +#!/usr/bin/env python
> +#
> +# Import CVS history into git
> +#
> +# Intended to be a near-workalike of Matthias Urlichs's Perl implementation.
> +#
> +# By Eric S. Raymond <esr@thyrsus.com>, December 2012
> +# May be redistributed under the license of the git project.
> +
> +import sys
> +
> +if sys.hexversion < 0x02060000:
> +    sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n")
> +    sys.exit(1)
> +
> +import os, getopt, subprocess, tempfile, shutil
> +
> +DEBUG_COMMANDS = 1
> +
> +class Fatal(Exception):
> +    "Unrecoverable error."
> +    def __init__(self, msg):
> +        Exception.__init__(self)
> +        self.msg = msg
> +
> +def do_or_die(dcmd, legend=""):
> +    "Either execute a command or raise a fatal exception."
> +    if legend:
> +        legend = " "  + legend
> +    if verbose >= DEBUG_COMMANDS:
> +        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
> +    try:
> +        retcode = subprocess.call(dcmd, shell=True)
> +        if retcode < 0:
> +            raise Fatal("git cvsimport: child was terminated by signal %d." % -retcode)
> +        elif retcode != 0:
> +            raise Fatal("git cvsimport: child returned %d." % retcode)
> +    except (OSError, IOError) as e:
> +        raise Fatal("git cvsimport: execution of %s%s failed: %s" % (dcmd, legend, e))
> +
> +def capture_or_die(dcmd, legend=""):
> +    "Either execute a command and capture its output or die."
> +    if legend:
> +        legend = " "  + legend
> +    if verbose >= DEBUG_COMMANDS:
> +        sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend))
> +    try:
> +        return subprocess.check_output(dcmd, shell=True)
> +    except subprocess.CalledProcessError as e:
> +        if e.returncode < 0:
> +            sys.stderr.write("git cvsimport: child was terminated by signal %d." % -e.returncode)
> +        elif e.returncode != 0:
> +            sys.stderr.write("git cvsimport: child returned %d." % e.returncode)
> +        sys.exit(1)
> +
> +class cvsps:
> +    "Method class for cvsps back end."
> +
> +    cvsps = "@@CVSPS3_PATH@@"
> +    def __init__(self):
> +        self.opts = ""
> +        self.revmap = None
> +    def set_repo(self, val):
> +        "Set the repository root option."
> +        if not val.startswith(":"):
> +            if not val.startswith(os.sep):
> +                val = os.path.abspath(val)
> +            val = ":local:" + val
> +        self.opts += " --root '%s'" % val
> +    def set_authormap(self, val):
> +        "Set the author-map file."
> +        self.opts += " -A '%s'" % val
> +    def set_fuzz(self, val):
> +        "Set the commit-similarity window."
> +        self.opts += " -z %s" % val
> +    def set_nokeywords(self):
> +        "Suppress CVS keyword expansion."
> +        self.opts += " -k"
> +    def add_opts(self, val):
> +        "Add options to the engine command line."
> +        self.opts += " " + val
> +    def set_exclusion(self, val):
> +        "Set a file exclusion regexp."
> +        self.opts += " -n -f '%s'" % val
> +    def set_after(self, val):
> +        "Set a date threshold for incremental import."
> +        self.opts += " -d '%s'" % val
> +    def set_revmap(self, val):
> +        "Set the file to which the engine should dump a reference map."
> +        self.revmap = val
> +        self.opts += " -R '%s'" % self.revmap
> +    def set_module(self, val):
> +        "Set the module to query."
> +        self.opts += " " + val
> +    def command(self):
> +        "Emit the command implied by all previous options."
> +        return self.cvsps + "--fast-export " + self.opts

"--fast-export" string is missing a leading space.  With this fix and
the latest cvsps build I'm seeing 6 of 15 failures for t9650 which is
what I was getting out of the patched t9600.

Chris

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH 3/3] cvsimport: start adding cvsps 3.x support
  2013-01-15  6:19           ` Chris Rorvick
@ 2013-01-15  6:44             ` Junio C Hamano
  0 siblings, 0 replies; 37+ messages in thread
From: Junio C Hamano @ 2013-01-15  6:44 UTC (permalink / raw)
  To: Chris Rorvick; +Cc: git, jrnieder, mhagger, esr

Chris Rorvick <chris@rorvick.com> writes:

[jc: please elide parts you are not responding to, leaving enough
lines to understand the context]

>> +    def command(self):
>> +        "Emit the command implied by all previous options."
>> +        return self.cvsps + "--fast-export " + self.opts
>
> "--fast-export" string is missing a leading space.  With this fix and
> the latest cvsps build I'm seeing 6 of 15 failures for t9650 which is
> what I was getting out of the patched t9600.

Thanks; I'll amend it and push the result out, but I think we will
need to rip this part out and form the command string in a saner way
(e.g. if the path needs to have SP in it, e.g. "/Program Files/cvsps3",
the above would not work correctly anyway).

^ permalink raw reply	[flat|nested] 37+ messages in thread

end of thread, other threads:[~2013-01-15  6:44 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-01-11  3:32 [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Junio C Hamano
2013-01-11 16:31 ` Junio C Hamano
2013-01-11 18:58   ` Eric S. Raymond
2013-01-11 19:17     ` Junio C Hamano
2013-01-11 19:27     ` Junio C Hamano
2013-01-12  5:04       ` Eric S. Raymond
2013-01-12  5:20 ` Junio C Hamano
2013-01-12  5:38   ` [PATCH] t/t960[123]: remove leftover scripts Junio C Hamano
2013-01-12  6:06     ` Chris Rorvick
2013-01-12  8:40   ` [PATCH] t/lib-cvs: cvsimport no longer works without Python >= 2.7 Junio C Hamano
2013-01-12 15:27     ` Michael Haggerty
2013-01-13 17:17       ` John Keeping
2013-01-12 15:47   ` [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Eric S. Raymond
2013-01-12 15:13 ` Michael Haggerty
2013-01-12 16:11   ` Eric S. Raymond
2013-01-12 18:16     ` Jonathan Nieder
2013-01-12 18:26   ` Jonathan Nieder
2013-01-13 22:20     ` Junio C Hamano
2013-01-13 23:27       ` Junio C Hamano
2013-01-14  1:40       ` [PATCH 0/3] A smoother transition plan for cvsimport Junio C Hamano
2013-01-14  1:40         ` [PATCH 1/3] cvsimport: allow setting a custom cvsps (2.x) program name Junio C Hamano
2013-01-14  1:40         ` [PATCH 2/3] cvsimport: introduce a version-switch wrapper Junio C Hamano
2013-01-14  1:47           ` Junio C Hamano
2013-01-14  1:40         ` [PATCH 3/3] cvsimport: start adding cvsps 3.x support Junio C Hamano
2013-01-15  6:19           ` Chris Rorvick
2013-01-15  6:44             ` Junio C Hamano
2013-01-14  5:12       ` [PATCH] cvsimport: rewrite to use cvsps 3.x to fix major bugs Michael Haggerty
2013-01-14  7:25       ` [PATCH v2 0/6] A smoother transition plan for cvsimport Junio C Hamano
2013-01-14  7:25         ` [PATCH v2 1/6] Makefile: add description on PERL/PYTHON_PATH Junio C Hamano
2013-01-14  7:25         ` [PATCH v2 2/6] cvsimport: allow setting a custom cvsps (2.x) program name Junio C Hamano
2013-01-14  7:25         ` [PATCH v2 3/6] cvsimport: introduce a version-switch wrapper Junio C Hamano
2013-01-14  7:25         ` [PATCH v2 4/6] cvsimport: start adding cvsps 3.x support Junio C Hamano
2013-01-14  7:25         ` [PATCH v2 5/6] cvsimport: make tests reusable for cvsimport-3 Junio C Hamano
2013-01-14  7:25         ` [PATCH v2 6/6] cvsimport-3: add a sample test Junio C Hamano
2013-01-14  7:47         ` [PATCH v2 0/6] A smoother transition plan for cvsimport Jonathan Nieder
2013-01-14  7:48         ` [PATCH v2 7/6] t9600: further prepare for sharing Junio C Hamano
2013-01-14  7:52         ` [PATCH v2 8/6] t9600: adjust for new cvsimport Junio C Hamano

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.