]> thyrsus.com Git - rcs-fast-import.git/commitdiff
Delete experimental rcs-fast-export, we have cvs-fast-export (different project)
authorEric S. Raymond <esr@thyrsus.com>
Thu, 5 Jun 2014 21:47:18 +0000 (17:47 -0400)
committerEric S. Raymond <esr@thyrsus.com>
Thu, 5 Jun 2014 21:47:18 +0000 (17:47 -0400)
rcs-fast-export [deleted file]

diff --git a/rcs-fast-export b/rcs-fast-export
deleted file mode 100644 (file)
index 3982f98..0000000
+++ /dev/null
@@ -1,872 +0,0 @@
-#!/usr/bin/env python
-"""
-TODO
-* Refactor commit coalescing
-* Add --strict-symbol-check to only coalesce commits if their symbol lists are equal
-* Further coalescing options? (e.g. small logfile differences)
-* Proper branching support in multi-file export
-* Optimize memory usage by discarding unneeded text
-"""
-
-import sys, os, stat
-import pprint  # Functional equivalent of Ruby pp?
-
-def usage():
-    print >>sys.stderr, """\
-rcs-fast-export [options] file [file ...]
-
-Fast-export the RCS history of one or more files. If a directory is specified,
-all RCS-tracked files in the directory and its descendants are exported.
-
-When importing single files, their pathname is discarded during import. When
-importing directories, only the specified directory component is discarded.
-
-When importing a single file, RCS commits are converted one by one. Otherwise,
-some heuristics is used to determine how to coalesce commits touching different
-files.
-
-Currently, commits are coalesced if they share the exact same log and if their
-date differs by no more than the user-specified fuzziness. Additionally, the
-symbols in one of the commit must be a subset of the symbols in the other
-commit, unless --no-symbol-check is specified or rcs.symbolCheck is set to
-false in the git configuration.
-
-Typical usage:
-    git init && rcs-fast-export.rb . | git fast-import && git reset --hard
-
-Options:
-       --help, -h, -?          display this help text
-       --authors-file, -A      specify a file containing username = Full Name <email> mappings
-       --rcs-commit-fuzz       fuzziness in RCS commits to be considered a single one when
-                               importing multiple files
-                               (in seconds, defaults to 300, i.e. 5 minutes)
-       --[no-]symbol-check     [do not] check symbols when coalescing commits
-       --[no-]tag-each-rev     [do not] create a lightweight tag for each RCS revision when
-                               importing a single file
-       --[no-]log-filename     [do not] prepend the filename to the commit log when importing
-                               a single file
-
-Config options:
-       rcs.authorsFile         for --authors-file
-       rcs.tagEachRev          for --tag-each-rev
-       rcs.logFilename         for --log-filename
-       rcs.commitFuzz          for --rcs-commit-fuzz
-       rcs.symbolCheck         for --rcs-symbol-check
-       rcs.tagFuzz             for --rcs-tag-fuzz
-"""
-
-def not_found(arg):
-    print >>sys.stderr,"Could not find %s" % arg
-
-def load_authors_file(fn):
-    "Returns a dict that maps usernames to author names & emails."
-    authdict = {}
-    try:
-        for line in open(fn):
-            (uname, author) = map(lambda x: x.strip(), line.split("-"))
-            if uname in hashdict:
-                print >>sys.stderr, "Username %(uname)s redefined to %(author)s" % locals()
-            authdict[uname] = author
-        return authdict
-    except OSError:
-        not_found(fn)
-
-class FatalException:
-    "Unrecoverable error."
-    def __init__(self, msg):
-        self.msg = msg
-
-class RCS:
-    RCS.marks = {}
-
-    @staticmethod
-    def clean(arg):
-        "Strip an optional final ;"
-        if arg.endswith(";"):
-            arg = arg[:-1]
-        return arg
-
-    @staticmethod
-    def sanitize(arg):
-        "Strip the first and last @, and de-double @@s"
-        if type(arg) == type([]):
-            if not arg[0].startswith("@@"):
-                raise FatalException('malformed first line')
-            if not (arg[-1][1] == "@" and arg[-1][-1] == "@"):
-                raise FatalException('malformed last line')
-            arg[0] = arg[0][1:]
-            arg[-1] = arg[-1][:-1]
-            arg = map(lambda s: s.replace("@@", "@"), arg)
-            return arg
-        elif type(arg) == type(""):
-            if arg.endswith("@"):
-                arg = arg[:-1]
-            if arg.startswith("@"):
-                arg = arg[1:]
-            arg = arg.replace("@@", "@")
-            return arg
-        else:
-            raise FatalException("unexpected argument type in sanitize")
-
-    @staticmethod
-    def at_clean(arg):
-        "Clean and sanitize."
-        return RCS.sanitize(RCS.clean(arg))
-
-    @staticmethod
-    def mark(key):
-        "Return a sequence mark for an object, which may be a blob or a commit."
-        if not key in RCS.marks:
-            RCS.marks[key] = len(RCS.marks)+1
-        return RCS.marks[key]
-
-    @staticmethod
-    def blob(file, rev):
-        return RCS.mark([file, rev])
-
-    @staticmethod
-    def commit(commit):
-        return RCS.mark(commit)
-
-    class File:
-        def __init__(master):
-            self.fname = master
-            self.head = None
-            self.comment = None
-            self.desc = []
-            self.revision = {} #Hash.new { |h, r| h[r] = Revision.new(self, r) }
-            executable = stat.S_IXUSER(os.stat(master)[stat.ST_MODE])
-            self.mode = '755' if executable else '644'
-
-        def has_revision(rev):
-            return rev in revision and self.revision[rev].author is not None
-
-        def export_commits(opts):
-            counter = 0
-            exported = []
-            while self.revision:
-                counter += 1
-
-                # a string sort is a very good candidate for
-                # export order, getting a miss only for
-                # multi-digit revision components
-                keys = self.revision.keys()
-                keys.sort()
-
-                print >>sys.stderr,"commit export loop %d" % counter
-                if exported:
-                    print >>sys.stderr,"\t#%d commits exported so far: %s" % ", ".join(exported)
-                print >>sys.stderr,"\t#%d to export: %s" % (len(keys), ",".join(keys))
-                for key in keys:
-                    rev = self.revision[key]
-                    # the parent commit is rev.next if we're on the
-                    # master branch (rev.branch is None) or
-                    # rev.diff_base otherwise
-                    if rev.branch is None:
-                        parent = rev.next
-                    else:
-                        parent = rev.diff_base
-                    # A commit can only be exported if it has no
-                    # parent, or if the parent has been exported
-                    # already. Skip this commit otherwise
-                    if parent and parent not in exported:
-                        continue
-
-                    branch = rev.branch or 'master'
-                    author = opts.authors[rev.author] or (rev.author + "  <empty>")
-                    date = `rev.date.tv_sec` + " +0000"
-                    log = ""
-                    if opts.log_filename:
-                        log += self.fname + ": "
-                    log += rev.log.to_s
-
-                    print "commit refs/heads/" + branch
-                    print "mark :" + RCS.commit(key)
-                    print "committer %s %s" % (author, date)
-                    print "data %d" % len(log)
-                    if log:
-                        print log
-                    if rev.branch_point:
-                        print "from :" + RCS.commit(parent)
-                    print "M %s :%s %s" % (RCS.blob(self.fname, key), fname)
-                    # TODO FIXME this *should* be safe, in
-                    # that it should not unduly move
-                    # branches back in time, but I'm not
-                    # 100% sure ...
-                    for sym in rev.branches:
-                        print "reset refs/heads/" + sym
-                        print "from :" + RCS.commit(key)
-                    for sym in rev.symbols:
-                        print "reset refs/tags/" + sym
-                        print "from :" + RCS.commit(key)
-                    if opts.tag_each_rev:
-                        print "reset refs/tags/" + key
-                        print "from :" + RCS.commit(key)
-                    exported.append(key)
-                for k in exported: del self.revision[k]
-
-    class Revision:
-        def __init__(fileobj, rev):
-            self.file = fileobj
-            self.rev = rev
-            self.author = None
-            self.date = None
-            self.state = None
-            self.next = None
-            self.branches = []
-            self.branch = None
-            self.branch_point = None
-            self.diff_base = None
-            self.log = []
-            self.text = []
-            self.symbols = set([])
-        def __hash__(self):
-            return hash(file, rev)
-        #def date=(str)
-        #        @date = Time.rcs(str)
-        #end
-        def blob(self):
-            str = "".join(self.text)
-            return "blob\nmark :#%s\ndata %d\n%s\n" \
-                   (RCS.blob(self.file.fname, self.rev), len(str), str)
-
-    @staticmethod
-    def parse(rcsfile):
-        rcs = RCS.File(rcsfile)
-        status = ["basic"]
-        rev = None
-        lines = []
-        difflines = []
-        linebuffer = []
-        with file(rcs.fname) as fp:
-            while True:
-                if linebuffer:
-                    line = linebuffer.pop()
-                else:
-                    line = fp.readline()
-                    if not line:
-                        break
-                if status[-1] == "basic":
-                    m = re.match(r"^([a-z]+)\s+([^;]*);?", line)
-                    if not m:
-                        raise FatalException("failed to find command")
-                    command = m.group(1)
-                    args = m.group(2)
-                    if not command:
-                        if debug:
-                            print >>sys.stderr,"Skipping empty command"
-                        continue
-                    if command == 'head':
-                        rcs.head = RCS.clean(args)
-                        continue
-                    if command == 'symbols':
-                        status.append('symbols')
-                        if args:
-                            linebuffer.append(args)
-                        continue
-                    if command == 'comment':
-                        rcs.comment = RCS.at_clean(args)
-                        continue
-                    m = re.match("^[0-9.]+$", command)
-                    if m:
-                        rev = command
-                        if rcs.has_revision(rev):
-                            status.append("revision_data")
-                        else:
-                            status.append("new_revision")
-                        continue
-                    if command == 'desc':
-                        status.append("desc")
-                        lines = []
-                        status.append("read_lines")
-                        continue
-                    if command in ('branch', 'access', 'locks', 'expand'):
-                        if debug:
-                            print >>sys.stderr,"Skipping unhandled command %s" % command
-                        status.append("skipping_lines")
-                        if args:
-                            linebuffer.apppend(args)
-                        continue
-                    raise FatalException("Unknown command %s" % command)
-                elif status[-1] == "skipping_lines":
-                    if line.strip.endswith(";"):
-                        status.pop()
-                        continue
-                elif status[-1] == "symbols":
-                    # we can have multiple symbols per line
-                    for pair in line.strip().split(";"):
-                        if not pair:
-                            status.pop()
-                        else:
-                            (sym, rev) = pair.strip().split(':')
-                            if rev:
-                                rcs.revision[rev].symbols.append(sym)
-                            else:
-                                status.pop()
-                elif status[-1] == "desc":
-                    rcs.desc = []
-                    status.pop()
-                elif status[-1] == "read_lines":
-                    # we sanitize lines as we read them
-                    # the first line must begin with a @, which we strip
-                    if not rcs.desc:
-                        if not line.startswith("@"):
-                            raise FatalException('malformed line')
-                        else:
-                            line = line[1:]
-
-                        # if the line ends with an ODD number of @, it's the
-                        # last line -- we work on actual_line so that content
-                        # such as @\n or @ work correctly (they would be
-                        # encoded respectively as ['@@@\n','@\n'] and
-                        # ['@@@@\n']
-                        ats = actual_line.chomp.match(/@+$/)
-                        if nomore = (ats && Regexp.last_match(0).length.odd?)
-                                actual_line.replace actual_line.chomp.sub(/@$/,'')
-                        end
-                        lines << actual_line.gsub('@@','@')
-                        if nomore
-                                status.pop
-                                redo
-                        end
-                when :new_revision
-                        case line.chomp
-                        when /^date\s+(\S+);\s+author\s+(\S+);\s+state\s+(\S+);$/
-                                rcs.revision[rev].date = $1
-                                rcs.revision[rev].author = $2
-                                rcs.revision[rev].state = $3
-                        when 'branches'
-                                status.push :branches
-                        when /branches\s*;/
-                                next
-                        when /^next\s+(\S+)?;$/
-                                nxt = rcs.revision[rev].next = $1
-                                next unless nxt
-                                raise "multiple diff_bases for #{nxt}" unless rcs.revision[nxt].diff_base.None?
-                                rcs.revision[nxt].diff_base = rev
-                                rcs.revision[nxt].branch = rcs.revision[rev].branch
-                        else
-                                status.pop
-                        end
-                when :branches
-                        candidate = line.split(';',2)
-                        branch = candidate.first.strip
-                        rcs.revision[rev].branches.push branch
-                        raise "multiple diff_bases for #{branch}" unless rcs.revision[branch].diff_base.None?
-                        rcs.revision[branch].diff_base = rev
-                        # we drop the last number from the branch name
-                        rcs.revision[branch].branch = branch.sub(/\.\d+$/,'.x')
-                        rcs.revision[branch].branch_point = rev
-                        status.pop if candidate.length > 1
-                when :revision_data
-                        case line.chomp
-                        when 'log'
-                                status.push :log
-                                lines.clear
-                                status.push :read_lines
-                        when 'text'
-                                if rev == rcs.head
-                                        status.push :head
-                                else
-                                        status.push :diff
-                                end
-                                lines.clear
-                                status.push :read_lines
-                        else
-                                status.pop
-                        end
-                when :log
-                        rcs.revision[rev].log.replace lines.dup
-                        status.pop
-                when :head
-                        rcs.revision[rev].text.replace lines.dup
-                        print rcs.revision[rev].blob
-                        status.pop
-                when :diff
-                        difflines.replace lines.dup
-                        difflines.pop if difflines.last.empty?
-                        base = rcs.revision[rev].diff_base
-                        unless rcs.revision[base].text
-                                pp rcs
-                                print rev, base
-                                raise 'no diff base!'
-                        end
-                        # deep copy
-                        buffer = []
-                        rcs.revision[base].text.each { |l| buffer << [l.dup] }
-
-                        adding = false
-                        index = None
-                        count = None
-
-                        while l = difflines.shift
-                                if adding
-                                        raise 'negative index during insertion' if index < 0
-                                        raise 'negative count during insertion' if count < 0
-                                        adding << l
-                                        count -= 1
-                                        # collected all the lines, put the before
-                                        unless count > 0
-                                                unless buffer[index]
-                                                        buffer[index] = []
-                                                end
-                                                buffer[index].unshift(*adding)
-                                                adding = false
-                                        end
-                                        next
-                                end
-
-                                l.chomp!
-                                raise 'malformed diff' unless l =~ /^([ad])(\d+) (\d+)$/
-                                diff_cmd = $1.intern
-                                index = $2.to_i
-                                count = $3.to_i
-                                case diff_cmd
-                                when :d
-                                        # for deletion, index 1 is the first index, so the Ruby
-                                        # index is one less than the diff one
-                                        index -= 1
-                                        # we replace them with empty string so that 'a' commands
-                                        # referring to the same line work properly
-                                        while count > 0
-                                                buffer[index].clear
-                                                index += 1
-                                                count -= 1
-                                        end
-                                when :a
-                                        # addition will prepend the appropriate lines
-                                        # to the given index, and in this case Ruby
-                                        # and diff indices are the same
-                                        adding = []
-                                end
-                        end
-
-                        # turn the buffer into an array of lines, deleting the empty ones
-                        buffer.delete_if { |l| l.empty? }
-                        buffer.flatten!
-
-                        rcs.revision[rev].text = buffer
-                        print rcs.revision[rev].blob
-                        status.pop
-                else
-                        raise "Unknown status #{status.last}"
-                end
-        end
-        end
-
-            # clean up the symbols/branches: look for revisions that have
-            # one or more symbols but no dates, and make them into
-            # branches, pointing to the highest commit with that key
-            branches = []
-            keys = rcs.revision.keys
-            rcs.revision.each do |key, rev|
-                    if rev.date.None? and not rev.symbols.empty?
-                            top = keys.select { |k| k.match(/^#{key}\./) }.sort.last
-                            tr = rcs.revision[top]
-                            raise "unhandled complex branch structure met: #{rev.inspect} refers #{tr.inspect}" if tr.date.None?
-                            tr.branches |= rev.symbols
-                            branches << key
-                    end
-            end
-            branches.each { |k| rcs.revision.delete k }
-
-            return rcs
-    end
-
-       class Tree
-               def initialize(commit)
-                       @commit = commit
-                       @files = Hash.new
-               end
-
-               def merge!(tree)
-                       testfiles = @files.dup
-                       tree.each { |rcs, rev| self.add(rcs, rev, testfiles) }
-                       # the next line is only reached if all the adds were
-                       # successful, so the merge is atomic
-                       @files.replace testfiles
-               end
-
-               def add(rcs, rev, file_list=@files)
-                       if file_list.key? rcs
-                               prev = file_list[rcs]
-                               if prev.log == rev.log
-                                       str = "re-adding existing file #{rcs.fname} (old: #{prev.rev}, new: #{rev.rev})"
-                               else
-                                       str = "re-adding existing file #{rcs.fname} (old: #{[prev.rev, prev.log.to_s].inspect}, new: #{[rev.rev, rev.log.to_s].inspect})"
-                               end
-                               if prev.text != rev.text
-                                       raise str
-                               else
-                                       @commit.warn_about str
-                               end
-                       end
-                       file_list[rcs] = rev
-               end
-
-               def each &block
-                       @files.each &block
-               end
-
-               def to_a
-                       files = []
-                       @files.map do |rcs, rev|
-                               files << "M #{rcs.mode} :#{RCS.blob rcs.fname, rev.rev} #{rcs.fname}"
-                       end
-                       files
-               end
-
-               def filenames
-                       @files.map { |rcs, rev| rcs.fname }
-               end
-
-               def to_s
-                       self.to_a.join("\n")
-               end
-       end
-
-       class Commit
-               attr_accessor :date, :log, :symbols, :author, :branch
-               attr_accessor :tree
-               def initialize(rcs, rev)
-                       raise NotImplementedError if rev.branch
-                       self.date = rev.date.dup
-                       self.log = rev.log.dup
-                       self.symbols = rev.symbols.dup
-                       self.author = rev.author
-                       self.branch = rev.branch
-
-                       self.tree = Tree.new self
-                       self.tree.add rcs, rev
-               end
-
-               def to_a
-                       [self.date, self.branch, self.symbols, self.author, self.log, self.tree.to_a]
-               end
-
-               def warn_about(str)
-                       warn str + " for commit on #{self.date}"
-               end
-
-               # Sort by date and then by number of symbols
-               def <=>(other)
-                       ds = self.date <=> other.date
-                       if ds != 0
-                               return ds
-                       else
-                               return self.symbols.length <=> other.symbols.length
-                       end
-               end
-
-               def merge!(commit)
-                       self.tree.merge! commit.tree
-                       if commit.date > self.date
-                               warn_about "updating date to #{commit.date}"
-                               self.date = commit.date
-                       end
-                       self.symbols.merge commit.symbols
-               end
-
-               def export(opts={})
-                       xbranch = self.branch || 'master'
-                       xauthor = opts[:authors][self.author] || "#{self.author} <empty>"
-                       xlog = self.log.to_s
-                       numdate = self.date.tv_sec
-                       xdate = "#{numdate} +0000"
-                       key = numdate.to_s
-
-                       print "commit refs/heads/#{xbranch}"
-                       print "mark :#{RCS.commit key}"
-                       print "committer #{xauthor} #{xdate}"
-                       print "data #{xlog.length}"
-                       print xlog unless xlog.empty?
-                       # TODO branching support for multi-file export
-                       # print "from :#{RCS.commit from}" if self.branch_point
-                       print self.tree.to_s
-
-                       # TODO branching support for multi-file export
-                       # rev.branches.each do |sym|
-                       #       print "reset refs/heads/#{sym}"
-                       #       print "from :#{RCS.commit key}"
-                       # end
-
-                       self.symbols.each do |sym|
-                               print "reset refs/tags/#{sym}"
-                               print "from :#{RCS.commit key}"
-                       end
-
-               end
-       end
-end
-
-require 'getoptlong'
-
-opts = GetoptLong.new(
-       # Authors file, like git-svn and git-cvsimport, more than one can be
-       # specified
-       ['--authors-file', '-A', GetoptLong::REQUIRED_ARGUMENT],
-       # RCS file suffix, like RCS
-       ['--rcs-suffixes', '-x', GetoptLong::REQUIRED_ARGUMENT],
-       # Date fuzziness for commits to be considered the same (in seconds)
-       ['--rcs-commit-fuzz', GetoptLong::REQUIRED_ARGUMENT],
-       # check symbols when coalescing?
-       ['--symbol-check', GetoptLong::NO_ARGUMENT],
-       ['--no-symbol-check', GetoptLong::NO_ARGUMENT],
-       # tag each revision?
-       ['--tag-each-rev', GetoptLong::NO_ARGUMENT],
-       ['--no-tag-each-rev', GetoptLong::NO_ARGUMENT],
-       # prepend filenames to commit logs?
-       ['--log-filename', GetoptLong::NO_ARGUMENT],
-       ['--no-log-filename', GetoptLong::NO_ARGUMENT],
-       ['--help', '-h', '-?', GetoptLong::NO_ARGUMENT]
-)
-
-# We read options in order, but they apply to all passed parameters.
-# TODO maybe they should only apply to the following, unless there's only one
-# file?
-opts.ordering = GetoptLong::RETURN_IN_ORDER
-
-file_list = []
-parse_options = {
-       :authors => Hash.new,
-       :commit_fuzz => 300,
-       :tag_fuzz => -1,
-}
-
-# Read config options
-`git config --get-all rcs.authorsfile`.each_line do |fn|
-       parse_options[:authors].merge! load_authors_file(fn.chomp)
-end
-
-parse_options[:tag_each_rev] = (
-       `git config --bool rcs.tageachrev`.chomp == 'true'
-) ? true : false
-
-parse_options[:log_filename] = (
-       `git config --bool rcs.logfilename`.chomp == 'true'
-) ? true : false
-
-fuzz = `git config --int rcs.commitFuzz`.chomp
-parse_options[:commit_fuzz] = fuzz.to_i unless fuzz.empty?
-
-fuzz = `git config --int rcs.tagFuzz`.chomp
-parse_options[:tag_fuzz] = fuzz.to_i unless fuzz.empty?
-
-parse_options[:symbol_check] = (
-       `git config --bool rcs.symbolcheck`.chomp == 'false'
-) ? false : true
-
-opts.each do |opt, arg|
-       case opt
-       when '--authors-file'
-               authors = load_authors_file(arg)
-               redef = parse_options[:authors].keys & authors.keys
-               print >>sys.stderr,"Authors file #{arg} redefines #{redef.join(', ')}" unless redef.empty?
-               parse_options[:authors].merge!(authors)
-       when '--rcs-suffixes'
-               # TODO
-       when '--rcs-commit-fuzz'
-               parse_options[:commit_fuzz] = arg.to_i
-       when '--rcs-tag-fuzz'
-               parse_options[:tag_fuzz] = arg.to_i
-       when '--symbol-check'
-               parse_options[:symbol_check] = true
-       when '--no-symbol-check'
-               parse_options[:symbol_check] = false
-       when '--tag-each-rev'
-               parse_options[:tag_each_rev] = true
-       when '--no-tag-each-rev'
-               # this is the default, which is fine since the missing key
-               # (default) returns None which is false in Ruby
-               parse_options[:tag_each_rev] = false
-       when '--log-filename'
-               parse_options[:log_filename] = true
-       when '--no-log-filename'
-               # this is the default, which is fine since the missing key
-               # (default) returns None which is false in Ruby
-               parse_options[:log_filename] = false
-       when ''
-               file_list << arg
-       when '--help'
-               usage
-               exit
-       end
-end
-
-if parse_options[:tag_fuzz] < parse_options[:commit_fuzz]
-       parse_options[:tag_fuzz] = parse_options[:commit_fuzz]
-end
-
-require 'etc'
-
-user = Etc.getlogin || ENV['USER']
-
-# steal username/email data from other init files that may contain the
-# information
-def steal_username
-       [
-               # the user's .hgrc file for a username field
-               ['~/.hgrc',   /^\s*username\s*=\s*(["'])?(.*)\1$/,       2],
-               # the user's .(g)vimrc for a changelog_username setting
-               ['~/.vimrc',  /changelog_username\s*=\s*(["'])?(.*)\1$/, 2],
-               ['~/.gvimrc', /changelog_username\s*=\s*(["'])?(.*)\1$/, 2],
-               []
-       ].each do |fn, rx, idx|
-               file = File.expand_path fn
-               if File.readable?(file) and File.read(file) =~ rx
-                       parse_options[:authors][user] = Regexp.last_match(idx).strip
-                       break
-               end
-       end
-end
-
-if user and not user.empty? and not parse_options[:authors].has_key?(user)
-       name = ENV['GIT_AUTHOR_NAME'] || ''
-       name.replace(`git config user.name`.chomp) if name.empty?
-       name.replace(Etc.getpwnam(user).gecos) if name.empty?
-
-       if name.empty?
-               # couldn't find a name, try to steal data from other sources
-               steal_username
-       else
-               # if we found a name, try to find an email too
-               email = ENV['GIT_AUTHOR_EMAIL'] || ''
-               email.replace(`git config user.email`.chomp) if email.empty?
-
-               if email.empty?
-                       # couldn't find an email, try to steal data too
-                       steal_username
-               else
-                       # we got both a name and email, fill the info
-                       parse_options[:authors][user] = "#{name} <#{email}>"
-               end
-       end
-end
-
-if file_list.empty?
-       usage
-       exit 1
-end
-
-SFX = ',v'
-
-status = 0
-
-rcs = []
-file_list.each do |arg|
-       case ftype = File.ftype(arg)
-       when 'file'
-               if arg[-2,2] == SFX
-                       if File.exists? arg
-                               rcsfile = arg.dup
-                       else
-                               not_found "RCS file #{arg}"
-                               status |= 1
-                       end
-                       filename = File.basename(arg, SFX)
-               else
-                       filename = File.basename(arg)
-                       path = File.dirname(arg)
-                       rcsfile = File.join(path, 'RCS', filename) + SFX
-                       unless File.exists? rcsfile
-                               rcsfile.replace File.join(path, filename) + SFX
-                               unless File.exists? rcsfile
-                                       not_found "RCS file for #{filename} in #{path}"
-                               end
-                       end
-               end
-               rcs << RCS.parse(filename, rcsfile)
-       when 'directory'
-               pattern = File.join(arg, '**', '*' + SFX)
-               Dir.glob(pattern).each do |rcsfile|
-                       filename = File.basename(rcsfile, SFX)
-                       path = File.dirname(rcsfile)
-                       path.sub!(/\/?RCS$/, '') # strip final /RCS if present
-                       path.sub!(/^#{Regexp.escape arg}\/?/, '') # strip initial dirname
-                       filename = File.join(path, filename) unless path.empty?
-                       begin
-                               rcs << RCS.parse(filename, rcsfile)
-                       rescue Exception => e
-                               print >>sys.stderr,"Failed to parse #{filename} @ #{rcsfile}:#{$.}"
-                               raise e
-                       end
-               end
-       else
-               print >>sys.stderr,"Cannot handle #{arg} of #{ftype} type"
-               status |= 1
-       end
-end
-
-if rcs.length == 1
-       rcs.first.export_commits(parse_options)
-else
-       print >>sys.stderr,"Preparing commits"
-
-       commits = []
-
-       rcs.each do |r|
-               r.revision.each do |k, rev|
-                       commits << RCS::Commit.new(r, rev)
-               end
-       end
-
-       print >>sys.stderr,"Sorting by date"
-
-       commits.sort!
-
-       if $DEBUG
-               print >>sys.stderr,"RAW commits (#{commits.length}):"
-               commits.each do |c|
-                       PP.pp c.to_a, $stderr
-               end
-       else
-               print >>sys.stderr,"#{commits.length} single-file commits"
-       end
-
-       print >>sys.stderr,"Coalescing [1] by date with fuzz #{parse_options[:commit_fuzz]}"
-
-       commits.reverse_each do |c|
-               commits.reverse_each do |k|
-                       break if k.date < c.date - parse_options[:commit_fuzz]
-                       next if k == c
-                       next if c.log != k.log or c.author != k.author or c.branch != k.branch
-                       next if k.date > c.date
-                       unless c.symbols.subset?(k.symbols) or k.symbols.subset?(c.symbols)
-                               if parse_options[:symbol_check]
-                                       print >>sys.stderr,"Not coalescing #{c.log.inspect}\n\tfor (#{c.tree.filenames.join(', ')})\n\tand (#{k.tree.filenames.join(', ')})"
-                                       print >>sys.stderr,"\tbecause their symbols disagree:\n\t#{c.symbols.to_a.inspect} and #{k.symbols.to_a.inspect} disagree on #{(c.symbols ^ k.symbols).to_a.inspect}"
-                                       print >>sys.stderr,"\tretry with the --no-symbol-check option if you want to merge these commits anyway"
-                                       next
-                               elsif $DEBUG
-                                       print >>sys.stderr,"Coalescing #{c.log.inspect}\n\tfor (#{c.tree.filenames.join(', ')})\n\tand (#{k.tree.filenames.join(', ')})"
-                                       print >>sys.stderr,"\twith disagreeing symbols:\n\t#{c.symbols.to_a.inspect} and #{k.symbols.to_a.inspect} disagree on #{(c.symbols ^ k.symbols).to_a.inspect}"
-                               end
-                       end
-                       begin
-                               c.merge! k
-                       rescue RuntimeError => err
-                               fuzz = c.date - k.date
-                               print >>sys.stderr,"Fuzzy commit coalescing failed: #{err}"
-                               print >>sys.stderr,"\tretry with commit fuzz < #{fuzz} if you don't want to see this message"
-                               break
-                       end
-                       commits.delete k
-               end
-       end
-
-       if $DEBUG
-               print >>sys.stderr,"[1] commits (#{commits.length}):"
-               commits.each do |c|
-                       PP.pp c.to_a, $stderr
-               end
-       else
-               print >>sys.stderr,"#{commits.length} coalesced commits"
-       end
-
-       commits.each { |c| c.export(parse_options) }
-
-end
-
-exit status
-       
-