From: Eric S. Raymond Date: Fri, 12 Nov 2010 04:24:57 +0000 (-0500) Subject: 11% translated. X-Git-Tag: 0.1~41 X-Git-Url: http://thyrsus.com/gitweb/?a=commitdiff_plain;h=250c637862582a1b1c081328f79fa991db091ec0;p=rcs-fast-import.git 11% translated. --- diff --git a/rcs-fast-export b/rcs-fast-export index ed873ab..858bced 100644 --- a/rcs-fast-export +++ b/rcs-fast-export @@ -1,30 +1,20 @@ -#!/usr/bin/ruby - -=begin +#!/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 -=end - -require 'pp' -require 'set' - -# Integer#odd? was introduced in Ruby 1.8.7, backport it to -# older versions -unless 2.respond_to? :odd? - class Integer - def odd? - self % 2 == 1 - end - end -end +* 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 +""" -def usage - STDERR.puts <>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. @@ -64,169 +54,164 @@ Config options: rcs.commitFuzz for --rcs-commit-fuzz rcs.symbolCheck for --rcs-symbol-check rcs.tagFuzz for --rcs-tag-fuzz - -EOM -end - -def not_found(arg) - STDERR.puts "Could not find #{arg}" -end - -# returns a hash that maps usernames to author names & emails -def load_authors_file(fn) - hash = {} - begin - File.open(File.expand_path(fn)) do |io| - io.each_line do |line| - uname, author = line.split('=', 2) - uname.strip! - author.strip! - STDERR.puts "Username #{uname} redefined to #{author}" if hash.has_key? uname - hash[uname] = author - end - end - rescue - not_found(fn) - end - return hash -end - -class Time - def Time.rcs(string) - fields = string.split('.') - raise ArgumentError, "wrong number of fields for RCS date #{string}" unless fields.length == 6 - Time.utc(*fields) - end -end - -module RCS - # strip an optional final ; - def RCS.clean(arg) - arg.chomp(';') - end - - # strip the first and last @, and de-double @@s - def RCS.sanitize(arg) - case arg - when Array - ret = arg.dup - raise 'malformed first line' unless ret.first[0,1] == '@' - raise 'malformed last line' unless ret.last[-1,1] == '@' - ret.first.sub!(/^@/,'') - ret.last.sub!(/@$/,'') - ret.map { |l| l.gsub('@@','@') } - when String - arg.chomp('@').sub(/^@/,'').gsub('@@','@') - else - raise - end - end - - # clean and sanitize - def RCS.at_clean(arg) - RCS.sanitize RCS.clean(arg) - end - - 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 - - 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 - - def has_revision?(rev) - @revision.has_key?(rev) and not @revision[rev].author.nil? - end - - 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} " - 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) } - end - end - end +""" + +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: + @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)) + + 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 + + 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 + + def has_revision?(rev) + @revision.has_key?(rev) and not @revision[rev].author.nil? + end + + 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} " + 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) } + end + end + end class Revision attr_accessor :rev, :author, :state, :next @@ -889,3 +874,6 @@ else end exit status + + +