From 8da5366df194d0b34f6e8081fff772525e8734fa Mon Sep 17 00:00:00 2001 From: "Eric S. Raymond" Date: Thu, 5 Jun 2014 17:47:18 -0400 Subject: [PATCH] Delete experimental rcs-fast-export, we have cvs-fast-export (different project) --- rcs-fast-export | 872 ------------------------------------------------ 1 file changed, 872 deletions(-) delete mode 100644 rcs-fast-export diff --git a/rcs-fast-export b/rcs-fast-export deleted file mode 100644 index 3982f98..0000000 --- a/rcs-fast-export +++ /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 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 + " ") - 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} " - 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 - - -- 2.43.0