On Tue, Jan 1, 2013 at 11:26 AM, Eric S. Raymond <esr@xxxxxxxxxxx> wrote: > diff --git a/git-cvsimport.py b/git-cvsimport.py > new file mode 100755 > index 0000000..6407e8a > --- /dev/null > +++ b/git-cvsimport.py > @@ -0,0 +1,342 @@ > +#!/usr/bin/env python > +# > +# Import CVS history into git > +# > +# Intended to be a near-workalike of Matthias Urlichs's Perl implementation. > +# > +# By Eric S. Raymond <esr@xxxxxxxxxxx>, December 2012 > +# May be redistributed under the license of the git project. > + > +import sys > + > +if sys.hexversion < 0x02060000: > + sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n") > + sys.exit(1) > + > +import os, getopt, subprocess, tempfile, shutil > + > +DEBUG_COMMANDS = 1 > + > +class Fatal(Exception): > + "Unrecoverable error." > + def __init__(self, msg): > + Exception.__init__(self) > + self.msg = msg > + > +def do_or_die(dcmd, legend=""): > + "Either execute a command or raise a fatal exception." > + if legend: > + legend = " " + legend > + if verbose >= DEBUG_COMMANDS: > + sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend)) > + try: > + retcode = subprocess.call(dcmd, shell=True) > + if retcode < 0: > + raise Fatal("git cvsimport: child was terminated by signal %d." % -retcode) > + elif retcode != 0: > + raise Fatal("git cvsimport: child returned %d." % retcode) > + except (OSError, IOError) as e: > + raise Fatal("git cvsimport: execution of %s%s failed: %s" % (dcmd, legend, e)) > + > +def capture_or_die(dcmd, legend=""): > + "Either execute a command and capture its output or die." > + if legend: > + legend = " " + legend > + if verbose >= DEBUG_COMMANDS: > + sys.stdout.write("git cvsimport: executing '%s'%s\n" % (dcmd, legend)) > + try: > + return subprocess.check_output(dcmd, shell=True) > + except subprocess.CalledProcessError as e: > + if e.returncode < 0: > + sys.stderr.write("git cvsimport: child was terminated by signal %d." % -e.returncode) > + elif e.returncode != 0: > + sys.stderr.write("git cvsimport: child returned %d." % e.returncode) > + sys.exit(1) > + > +class cvsps: > + "Method class for cvsps back end." > + def __init__(self): > + self.opts = "" > + self.revmap = None > + def set_repo(self, val): > + "Set the repository root option." > + if not val.startswith(":"): > + if not val.startswith(os.sep): > + val = os.path.abspath(val) > + val = ":local:" + val > + self.opts += " --root '%s'" % val > + def set_authormap(self, val): > + "Set the author-map file." > + self.opts += " -A '%s'" % val > + def set_fuzz(self, val): > + "Set the commit-similarity window." > + self.opts += " -z %s" % val > + def set_nokeywords(self): > + "Suppress CVS keyword expansion." > + self.opts += " -k" > + def add_opts(self, val): > + "Add options to the engine command line." > + self.opts += " " + val > + def set_exclusion(self, val): > + "Set a file exclusion regexp." > + self.opts += " -n -f '%s'" % val > + def set_after(self, val): > + "Set a date threshold for incremental import." > + self.opts += " -d '%s'" % val > + def set_revmap(self, val): > + "Set the file to which the engine should dump a reference map." > + self.revmap = val > + self.opts += " -R '%s'" % self.revmap > + def set_module(self, val): > + "Set the module to query." > + self.opts += " " + val > + def command(self): > + "Emit the command implied by all previous options." > + return "cvsps --fast-export " + self.opts > + > +class cvs2git: > + "Method class for cvs2git back end." > + def __init__(self): > + self.opts = "" > + self.modulepath = "." > + def set_authormap(self, _val): > + "Set the author-map file." > + sys.stderr.write("git cvsimport: author maping is not supported with cvs2git.\n") > + sys.exit(1) > + def set_repo(self, _val): > + "Set the repository root option." > + sys.stderr.write("git cvsimport: cvs2git must run within a repository checkout directory.\n") > + sys.exit(1) > + def set_fuzz(self, _val): > + "Set the commit-similarity window." > + sys.stderr.write("git cvsimport: fuzz setting is not supported with cvs2git.\n") > + sys.exit(1) > + def set_nokeywords(self): > + "Suppress CVS keyword expansion." > + self.opts += " --keywords-off" > + def add_opts(self, val): > + "Add options to the engine command line." > + self.opts += " " + val > + def set_exclusion(self, val): > + "Set a file exclusion regexp." > + self.opts += " --exclude='%s'" % val > + def set_after(self, _val): > + "Set a date threshold for incremental import." > + sys.stderr.write("git cvsimport: incremental import is not supported with cvs2git.\n") > + sys.exit(1) > + def set_revmap(self, _val): > + "Set the file to which the engine should dump a reference map." > + sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n") > + sys.exit(1) > + def set_module(self, val): > + "Set the module to query." > + self.modulepath = " " + val > + def command(self): > + "Emit the command implied by all previous options." > + return "(cvs2git --username=git-cvsimport --quiet --quiet --blobfile={0} --dumpfile={1} {2} {3} && cat {0} {1} && rm {0} {1})".format(tempfile.mkstemp()[1], tempfile.mkstemp()[1], self.opts, self.modulepath) > + > +class filesource: > + "Method class for file-source back end." > + def __init__(self, filename): > + self.filename = filename > + def __complain(self, legend): > + sys.stderr.write("git cvsimport: %s with file source.\n" % legend) > + sys.exit(1) > + def set_repo(self, _val): > + "Set the repository root option." > + self.__complain("repository can't be set") > + def set_authormap(self, _val): > + "Set the author-map file." > + sys.stderr.write("git cvsimport: author maping is not supported with filesource.\n") > + sys.exit(1) > + def set_fuzz(self, _val): > + "Set the commit-similarity window." > + self.__complain("fuzz can't be set") > + def set_nokeywords(self, _val): > + "Suppress CVS keyword expansion." > + self.__complain("keyword suppression can't be set") > + def add_opts(self, _val): > + "Add options to the engine command line." > + self.__complain("other options can't be set") > + def set_exclusion(self, _val): > + "Set a file exclusion regexp." > + self.__complain("exclusions can't be set") > + def set_after(self, _val): > + "Set a date threshold for incremental import." > + pass > + def set_revmap(self, _val): > + "Set the file to which the engine should dump a reference map." > + sys.stderr.write("git cvsimport: can't get a reference map from cvs2git.\n") > + sys.exit(1) > + def set_module(self, _val): > + "Set the module to query." > + self.__complain("module can't be set") > + def command(self): > + "Emit the command implied by all previous options." > + return "cat " + self.filename > + > +if __name__ == '__main__': > + if sys.hexversion < 0x02060000: > + sys.stderr.write("git cvsimport: requires Python 2.6 or later.\n") > + sys.exit(1) > + (options, arguments) = getopt.getopt(sys.argv[1:], "vbe:d:C:r:o:ikus:p:z:P:S:aL:A:Rh") > + verbose = 0 > + bare = False > + root = None > + outdir = os.getcwd() > + remotize = False > + import_only = False > + underscore_to_dot = False > + slashsubst = None > + authormap = None > + revisionmap = False > + backend = cvsps() > + for (opt, val) in options: > + if opt == '-v': > + verbose += 1 > + elif opt == '-b': > + bare = True > + elif opt == '-e': > + for cls in (cvsps, cvs2git): > + if cls.__name__ == val: > + backend = cls() > + break > + else: > + sys.stderr.write("git cvsimport: unknown engine %s.\n" % val) > + sys.exit(1) > + elif opt == '-d': > + backend.set_repo(val) > + elif opt == '-C': > + outdir = val > + elif opt == '-r': > + remotize = True > + elif opt == '-o': > + sys.stderr.write("git cvsimport: -o is no longer supported.\n") > + sys.exit(1) > + elif opt == '-i': > + import_only = True > + elif opt == '-k': > + backend.set_nokeywords() > + elif opt == '-u': > + underscore_to_dot = True > + elif opt == '-s': > + slashsubst = val > + elif opt == '-p': > + backend.add_opts(val.replace(",", " ")) > + elif opt == '-z': > + backend.set_fuzz(val) > + elif opt == '-P': > + backend = filesource(val) > + sys.exit(1) > + elif opt in ('-m', '-M'): > + sys.stderr.write("git cvsimport: -m and -M are no longer supported: use reposurgeon instead.\n") > + sys.exit(1) > + elif opt == '-S': > + backend.set_exclusion(val) > + elif opt == '-a': > + sys.stderr.write("git cvsimport: -a is no longer supported.\n") > + sys.exit(1) > + elif opt == '-L': > + sys.stderr.write("git cvsimport: -L is no longer supported.\n") > + sys.exit(1) > + elif opt == '-A': > + authormap = os.path.abspath(val) > + elif opt == '-R': > + revisionmap = True > + else: > + print """\ > +git cvsimport [-A <author-conv-file>] [-C <git_repository>] [-b] [-d <CVSROOT>] > + [-e engine] [-h] [-i] [-k] [-p <options-for-cvsps>] [-P <source-file>] > + [-r <remote>] [-R] [-s <subst>] [-S <regex>] [-u] [-v] [-z <fuzz>] > + [<CVS_module>] > +""" > + > + def metadata(fn): > + if bare: > + return fn > + else: > + return os.path.join(".git", fn) > + try: > + if outdir: > + try: > + # If the output directory does not exist, create it > + # and initialize it as a git repository. > + os.mkdir(outdir) > + do_or_die("git init --quiet " + outdir) > + except OSError: > + # Otherwise, assume user wants incremental import. > + if not bare and not os.path.exists(os.path.join(outdir, ".git")): > + raise Fatal("output directory is not a git repository") > + threshold = capture_or_die("git log -1 --format=%ct").strip() > + backend.set_after(threshold) > + if revisionmap: > + backend.set_revmap(tempfile.mkstemp()[1]) > + markmap = tempfile.mkstemp()[1] > + if arguments: > + backend.set_module(arguments[0]) > + gitopts = "" > + if bare: > + gitopts += " --bare" > + if revisionmap: > + gitopts += " --export-marks='%s'" % markmap > + if authormap: > + shutil.copyfile(authormap, metadata("cvs_authors")) > + if os.path.exists(metadata("cvs-authors")): > + backend.set_authormap(metadata("cvs-authors")) > + do_or_die("%s | (cd %s >/dev/null; git fast-import --quiet %s)" \ > + % (backend.command(), outdir, gitopts)) outdir needs to be quoted in the formatted string, i.e.: "%s | (cd '%s' >/dev/null ..." Also, I noticed the generated cvs-revisions file now maps cvs revisions to blobs instead of commits. Was this change intentional? Thanks, Chris -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html