]> thyrsus.com Git - rcs-fast-import.git/commitdiff
Checkpoint. 32% translated.
authorEric S. Raymond <esr@thyrsus.com>
Fri, 12 Nov 2010 15:56:38 +0000 (10:56 -0500)
committerEric S. Raymond <esr@thyrsus.com>
Fri, 12 Nov 2010 15:56:38 +0000 (10:56 -0500)
rcs-fast-export

index 858bcedc327e192de924e80a4d62f6f8cf551f95..33c6133456e198f8370d1ae59129f5ff6d1a6a30 100644 (file)
@@ -8,11 +8,10 @@ TODO
 * Optimize memory usage by discarding unneeded text
 """
 
-import sys
+import sys, os, stat
 import pprint  # Functional equivalent of Ruby pp?
 
 def usage():
-
     print >>sys.stderr, """\
 rcs-fast-export [options] file [file ...]
 
@@ -78,16 +77,18 @@ class FatalException:
         self.msg = msg
 
 class RCS:
+    RCS.marks = {}
+
     @staticmethod
     def clean(arg):
-        # strip an optional final ;
+        "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
+        "Strip the first and last @, and de-double @@s"
         if type(arg) == type([]):
             if not arg[0].startswith("@@"):
                 raise FatalException('malformed first line')
@@ -109,375 +110,371 @@ class RCS:
 
     @staticmethod
     def at_clean(arg):
-        # clean and sanitize
+        "Clean and sanitize."
         return RCS.sanitize(RCS.clean(arg))
 
-    def RCS.mark(key)
-            @@marks ||= {}
-            if @@marks.key? key
-                    @@marks[key]
-            else
-                    @@marks[key] = @@marks.length + 1
-            end
-    end
-
-    def RCS.blob(file, rev)
-            RCS.mark([file, rev])
-    end
-
-    def RCS.commit(commit)
-            RCS.mark(commit)
-    end
+    @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]
 
-    class File
-            attr_accessor :head, :comment, :desc, :revision, :fname, :mode
-            def initialize(fname, executable)
-                    @fname = fname.dup
-                    @head = nil
-                    @comment = nil
-                    @desc = []
-                    @revision = Hash.new { |h, r| h[r] = Revision.new(self, r) }
-                    @mode = executable ? '755' : '644'
-            end
+    @staticmethod
+    def blob(file, rev):
+        return RCS.mark([file, rev])
 
-            def has_revision?(rev)
-                    @revision.has_key?(rev) and not @revision[rev].author.nil?
-            end
+    @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)
 
-            def export_commits(opts={})
-                    counter = 0
-                    exported = []
-                    until @revision.empty?
-                            counter += 1
-
-                            # a string sort is a very good candidate for
-                            # export order, getting a miss only for
-                            # multi-digit revision components
-                            keys = @revision.keys.sort
-
-                            STDERR.puts "commit export loop ##{counter}"
-                            STDERR.puts "\t#{exported.length} commits exported so far: #{exported.join(', ')}" unless exported.empty?
-                            STDERR.puts "\t#{keys.size} to export: #{keys.join(', ')}"
-
-                            keys.each do |key|
-                                    rev = @revision[key]
-                                    # the parent commit is rev.next if we're on the
-                                    # master branch (rev.branch is nil) or
-                                    # rev.diff_base otherwise
-                                    from = rev.branch.nil? ? rev.next : 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 from and not exported.include? from
-                                            next
-                                    end
-
-                                    branch = rev.branch || 'master'
-                                    author = opts[:authors][rev.author] || "#{rev.author} <empty>"
-                                    date = "#{rev.date.tv_sec} +0000"
-                                    log = String.new
-                                    if opts[:log_filename]
-                                            log << @fname << ": "
-                                    end
-                                    log << rev.log.to_s
-
-                                    puts "commit refs/heads/#{branch}"
-                                    puts "mark :#{RCS.commit key}"
-                                    puts "committer #{author} #{date}"
-                                    puts "data #{log.length}"
-                                    puts log unless log.empty?
-                                    puts "from :#{RCS.commit from}" if rev.branch_point
-                                    puts "M #{@mode} :#{RCS.blob @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 ...
-                                    rev.branches.each do |sym|
-                                            puts "reset refs/heads/#{sym}"
-                                            puts "from :#{RCS.commit key}"
-                                    end
-                                    rev.symbols.each do |sym|
-                                            puts "reset refs/tags/#{sym}"
-                                            puts "from :#{RCS.commit key}"
-                                    end
-                                    if opts[:tag_each_rev]
-                                            puts "reset refs/tags/#{key}"
-                                            puts "from :#{RCS.commit key}"
-                                    end
-
-                                    exported.push key
-                            end
-                            exported.each { |k| @revision.delete(k) }
+    @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.appand("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()
+                when :desc
+                        rcs.desc.replace lines.dup
+                        status.pop
+                when :read_lines
+                        # we sanitize lines as we read them
+
+                        actual_line = line.dup
+
+                        # the first line must begin with a @, which we strip
+                        if lines.empty?
+                                ats = line.match(/^@+/)
+                                raise 'malformed line' unless ats
+                                actual_line.replace line.sub(/^@/,'')
+                        end
+
+                        # 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
-    end
-
-       class Revision
-               attr_accessor :rev, :author, :state, :next
-               attr_accessor :branches, :log, :text, :symbols
-               attr_accessor :branch, :diff_base, :branch_point
-               attr_reader   :date
-               def initialize(file, rev)
-                       @file = file
-                       @rev = rev
-                       @author = nil
-                       @date = nil
-                       @state = nil
-                       @next = nil
-                       @branches = []
-                       @branch = nil
-                       @branch_point = nil
-                       @diff_base = nil
-                       @log = []
-                       @text = []
-                       @symbols = Set.new
-               end
-
-               def date=(str)
-                       @date = Time.rcs(str)
-               end
-
-               def blob
-                       str = @text.join('')
-                       ret = "blob\nmark :#{RCS.blob @file.fname, @rev}\ndata #{str.length}\n#{str}\n"
-                       ret
-               end
-       end
-
-       def RCS.parse(fname, rcsfile)
-               rcs = RCS::File.new(fname, ::File.executable?(rcsfile))
-
-               ::File.open(rcsfile, 'r') do |file|
-                       status = [:basic]
-                       rev = nil
-                       lines = []
-                       difflines = []
-                       file.each_line do |line|
-                               case status.last
-                               when :basic
-                                       command, args = line.split($;,2)
-                                       next if command.empty?
-
-                                       if command.chomp!(';')
-                                               STDERR.puts "Skipping empty command #{command.inspect}" if $DEBUG
-                                               next
-                                       end
-
-                                       case command
-                                       when 'head'
-                                               rcs.head = RCS.clean(args.chomp)
-                                       when 'symbols'
-                                               status.push :symbols
-                                               next if args.empty?
-                                               line = args; redo
-                                       when 'comment'
-                                               rcs.comment = RCS.at_clean(args.chomp)
-                                       when /^[0-9.]+$/
-                                               rev = command.dup
-                                               if rcs.has_revision?(rev)
-                                                       status.push :revision_data
-                                               else
-                                                       status.push :new_revision
-                                               end
-                                       when 'desc'
-                                               status.push :desc
-                                               lines.clear
-                                               status.push :read_lines
-                                       when 'branch', 'access', 'locks', 'expand'
-                                               STDERR.puts "Skipping unhandled command #{command.inspect}" if $DEBUG
-                                               status.push :skipping_lines
-                                               next if args.empty?
-                                               line = args; redo
-                                       else
-                                               raise "Unknown command #{command.inspect}"
-                                       end
-                               when :skipping_lines
-                                       status.pop if line.strip.chomp!(';')
-                               when :symbols
-                                       # we can have multiple symbols per line
-                                       pairs = line.strip.split($;)
-                                       pairs.each do |pair|
-                                               sym, rev = pair.strip.split(':',2);
-                                               if rev
-                                                       status.pop if rev.chomp!(';')
-                                                       rcs.revision[rev].symbols << sym
-                                               else
-                                                       status.pop
-                                               end
-                                       end
-                               when :desc
-                                       rcs.desc.replace lines.dup
-                                       status.pop
-                               when :read_lines
-                                       # we sanitize lines as we read them
-
-                                       actual_line = line.dup
-
-                                       # the first line must begin with a @, which we strip
-                                       if lines.empty?
-                                               ats = line.match(/^@+/)
-                                               raise 'malformed line' unless ats
-                                               actual_line.replace line.sub(/^@/,'')
-                                       end
-
-                                       # 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.nil?
-                                               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.nil?
-                                       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
-                                       puts 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
-                                               puts rev, base
-                                               raise 'no diff base!'
-                                       end
-                                       # deep copy
-                                       buffer = []
-                                       rcs.revision[base].text.each { |l| buffer << [l.dup] }
-
-                                       adding = false
-                                       index = nil
-                                       count = nil
-
-                                       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
-                                       puts 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.nil? 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.nil?
-                               tr.branches |= rev.symbols
-                               branches << key
-                       end
-               end
-               branches.each { |k| rcs.revision.delete k }
+            branches.each { |k| rcs.revision.delete k }
 
-               return rcs
-       end
+            return rcs
+    end
 
        class Tree
                def initialize(commit)
@@ -581,24 +578,24 @@ class RCS:
                        xdate = "#{numdate} +0000"
                        key = numdate.to_s
 
-                       puts "commit refs/heads/#{xbranch}"
-                       puts "mark :#{RCS.commit key}"
-                       puts "committer #{xauthor} #{xdate}"
-                       puts "data #{xlog.length}"
-                       puts xlog unless xlog.empty?
+                       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
-                       # puts "from :#{RCS.commit from}" if self.branch_point
-                       puts self.tree.to_s
+                       # 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|
-                       #       puts "reset refs/heads/#{sym}"
-                       #       puts "from :#{RCS.commit key}"
+                       #       print "reset refs/heads/#{sym}"
+                       #       print "from :#{RCS.commit key}"
                        # end
 
                        self.symbols.each do |sym|
-                               puts "reset refs/tags/#{sym}"
-                               puts "from :#{RCS.commit key}"
+                               print "reset refs/tags/#{sym}"
+                               print "from :#{RCS.commit key}"
                        end
 
                end
@@ -667,7 +664,7 @@ opts.each do |opt, arg|
        when '--authors-file'
                authors = load_authors_file(arg)
                redef = parse_options[:authors].keys & authors.keys
-               STDERR.puts "Authors file #{arg} redefines #{redef.join(', ')}" unless redef.empty?
+               print >>sys.stderr,"Authors file #{arg} redefines #{redef.join(', ')}" unless redef.empty?
                parse_options[:authors].merge!(authors)
        when '--rcs-suffixes'
                # TODO
@@ -683,13 +680,13 @@ opts.each do |opt, arg|
                parse_options[:tag_each_rev] = true
        when '--no-tag-each-rev'
                # this is the default, which is fine since the missing key
-               # (default) returns nil which is false in Ruby
+               # (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 nil which is false in Ruby
+               # (default) returns None which is false in Ruby
                parse_options[:log_filename] = false
        when ''
                file_list << arg
@@ -793,12 +790,12 @@ file_list.each do |arg|
                        begin
                                rcs << RCS.parse(filename, rcsfile)
                        rescue Exception => e
-                               STDERR.puts "Failed to parse #{filename} @ #{rcsfile}:#{$.}"
+                               print >>sys.stderr,"Failed to parse #{filename} @ #{rcsfile}:#{$.}"
                                raise e
                        end
                end
        else
-               STDERR.puts "Cannot handle #{arg} of #{ftype} type"
+               print >>sys.stderr,"Cannot handle #{arg} of #{ftype} type"
                status |= 1
        end
 end
@@ -806,7 +803,7 @@ end
 if rcs.length == 1
        rcs.first.export_commits(parse_options)
 else
-       STDERR.puts "Preparing commits"
+       print >>sys.stderr,"Preparing commits"
 
        commits = []
 
@@ -816,20 +813,20 @@ else
                end
        end
 
-       STDERR.puts "Sorting by date"
+       print >>sys.stderr,"Sorting by date"
 
        commits.sort!
 
        if $DEBUG
-               STDERR.puts "RAW commits (#{commits.length}):"
+               print >>sys.stderr,"RAW commits (#{commits.length}):"
                commits.each do |c|
                        PP.pp c.to_a, $stderr
                end
        else
-               STDERR.puts "#{commits.length} single-file commits"
+               print >>sys.stderr,"#{commits.length} single-file commits"
        end
 
-       STDERR.puts "Coalescing [1] by date with fuzz #{parse_options[:commit_fuzz]}"
+       print >>sys.stderr,"Coalescing [1] by date with fuzz #{parse_options[:commit_fuzz]}"
 
        commits.reverse_each do |c|
                commits.reverse_each do |k|
@@ -839,21 +836,21 @@ else
                        next if k.date > c.date
                        unless c.symbols.subset?(k.symbols) or k.symbols.subset?(c.symbols)
                                if parse_options[:symbol_check]
-                                       STDERR.puts "Not coalescing #{c.log.inspect}\n\tfor (#{c.tree.filenames.join(', ')})\n\tand (#{k.tree.filenames.join(', ')})"
-                                       STDERR.puts "\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}"
-                                       STDERR.puts "\tretry with the --no-symbol-check option if you want to merge these commits anyway"
+                                       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
-                                       STDERR.puts "Coalescing #{c.log.inspect}\n\tfor (#{c.tree.filenames.join(', ')})\n\tand (#{k.tree.filenames.join(', ')})"
-                                       STDERR.puts "\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}"
+                                       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
-                               STDERR.puts "Fuzzy commit coalescing failed: #{err}"
-                               STDERR.puts "\tretry with commit fuzz < #{fuzz} if you don't want to see this message"
+                               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
@@ -861,12 +858,12 @@ else
        end
 
        if $DEBUG
-               STDERR.puts "[1] commits (#{commits.length}):"
+               print >>sys.stderr,"[1] commits (#{commits.length}):"
                commits.each do |c|
                        PP.pp c.to_a, $stderr
                end
        else
-               STDERR.puts "#{commits.length} coalesced commits"
+               print >>sys.stderr,"#{commits.length} coalesced commits"
        end
 
        commits.each { |c| c.export(parse_options) }
@@ -874,6 +871,5 @@ else
 end
 
 exit status
-