6 STDERR.puts "#{$0} filename -- fast-export filename's RCS history"
10 STDERR.puts "Could not find #{arg}"
15 fields = string.split('.')
16 raise ArgumentError, "wrong number of fields for RCS date #{string}" unless fields.length == 6
22 # strip an optional final ;
27 # strip the first and last @, and de-double @@s
32 raise 'malformed first line' unless ret.first[0,1] == '@'
33 raise 'malformed last line' unless ret.last[-1,1] == '@'
34 ret.first.sub!(/^@/,'')
35 ret.last.sub!(/@$/,'')
36 ret.map { |l| l.gsub('@@','@') }
38 arg.chomp('@').sub(/^@/,'').gsub('@@','@')
46 RCS.sanitize RCS.clean(arg)
50 arg.gsub('.', '0') + ('90'*5)
54 arg.gsub('.', '0') + ('09'*5)
58 attr_accessor :head, :comment, :desc, :revision
64 @revision = Hash.new { |h, r| h[r] = Revision.new(r) }
67 def has_revision?(rev)
68 @revision.has_key?(rev) and not @revision[rev].author.nil?
74 until @revision.empty?
77 # a string sort is a very good candidate for
78 # export order, getting a miss only for
79 # multi-digit revision components
80 keys = @revision.keys.sort
82 STDERR.puts "commit export loop ##{counter}"
83 STDERR.puts "\t#{exported.length} commits exported so far: #{exported.join(', ')}" unless exported.empty?
84 STDERR.puts "\t#{keys.size} to export: #{keys.join(', ')}"
88 # the parent commit is rev.next if we're on the
89 # master branch (rev.branch is nil) or
90 # rev.diff_base otherwise
91 from = rev.branch.nil? ? rev.next : rev.diff_base
92 # A commit can only be exported if it has no
93 # parent, or if the parent has been exported
94 # already. Skip this commit otherwise
95 if from and not exported.include? from
99 branch = rev.branch || 'master'
100 # TODO map authors to author/email
101 author = "#{rev.author} <empty>"
102 date = "#{rev.date.tv_sec} +0000"
105 puts "commit refs/heads/#{branch}"
106 puts "mark :#{RCS.commit key}"
107 puts "committer #{author} #{date}"
108 puts "data #{log.length}"
109 puts log unless log.empty?
110 puts "from :#{RCS.commit from}" if rev.branch_point
111 puts "M 644 :#{RCS.blob key} #{@fname}"
114 exported.each { |k| @revision.delete(k) }
120 attr_accessor :rev, :author, :date, :state, :next
121 attr_accessor :branches, :log, :text
122 attr_accessor :branch, :diff_base, :branch_point
138 @date = Time.rcs(str)
143 ret = "blob\nmark :#{RCS.blob @rev}\ndata #{str.length}\n#{str}\n"
148 def RCS.parse(fname, rcsfile, opts={})
149 rcs = RCS::File.new(fname)
151 ::File.open(rcsfile, 'r') do |file|
156 file.each_line do |line|
159 command, args = line.split($;,2)
160 next if command.empty?
164 rcs.head = RCS.clean(args.chomp)
166 rcs.comment = RCS.at_clean(args.chomp)
169 if rcs.has_revision?(rev)
170 status.push :revision_data
172 status.push :new_revision
177 status.push :read_lines
179 STDERR.puts "Skipping unhandled command #{command.inspect}"
182 rcs.desc.replace lines.dup
185 # we sanitize lines as we read them
187 actual_line = line.dup
189 # the first line must begin with a @, which we strip
191 ats = line.match(/^@+/)
192 raise 'malformed line' unless ats
193 actual_line.replace line.sub(/^@/,'')
196 # if the line ends with an ODD number of @, it's the
197 # last line -- we work on actual_line so that content
198 # such as @\n or @ work correctly (they would be
199 # encoded respectively as ['@@@\n','@\n'] and
201 ats = actual_line.chomp.match(/@+$/)
202 if nomore = (ats && Regexp.last_match(0).length.odd?)
203 actual_line.replace actual_line.chomp.sub(/@$/,'')
205 lines << actual_line.gsub('@@','@')
212 when /^date\s+(\S+);\s+author\s+(\S+);\sstate\s(\S+);$/
213 rcs.revision[rev].date = $1
214 rcs.revision[rev].author = $2
215 rcs.revision[rev].state = $3
217 status.push :branches
220 when /^next\s+(\S+)?;$/
221 nxt = rcs.revision[rev].next = $1
223 raise "multiple diff_bases for #{nxt}" unless rcs.revision[nxt].diff_base.nil?
224 rcs.revision[nxt].diff_base = rev
225 rcs.revision[nxt].branch = rcs.revision[rev].branch
230 candidate = line.split(';',2)
231 branch = candidate.first.strip
232 rcs.revision[rev].branches.push branch
233 raise "multiple diff_bases for #{branch}" unless rcs.revision[branch].diff_base.nil?
234 rcs.revision[branch].diff_base = rev
235 # we drop the last number from the branch name
236 rcs.revision[branch].branch = branch.sub(/\.\d+$/,'')
237 rcs.revision[branch].branch_point = rev
238 status.pop if candidate.length > 1
244 status.push :read_lines
252 status.push :read_lines
257 rcs.revision[rev].log.replace lines.dup
260 rcs.revision[rev].text.replace lines.dup
261 puts rcs.revision[rev].blob
264 difflines.replace lines.dup
265 difflines.pop if difflines.last.empty?
266 base = rcs.revision[rev].diff_base
267 unless rcs.revision[base].text
270 raise 'no diff base!'
274 rcs.revision[base].text.each { |l| buffer << l.dup }
280 while l = difflines.shift
284 adding = false unless count > 0
289 raise 'malformed diff' unless l =~ /^([ad])(\d+) (\d+)$/
294 # we replace them with empty string so that 'a' commands
295 # referring to the same line work properly
297 buffer[index].replace ''
307 buffer.delete_if { |l| l.empty? }
309 rcs.revision[rev].text = buffer
310 puts rcs.revision[rev].blob
313 STDERR.puts "Unknown status #{status.last}"
335 not_found "RCS file #{arg}"
338 filename = File.basename(arg, SFX)
340 filename = File.basename(arg)
341 path = File.dirname(arg)
342 rcsfile = File.join(path, 'RCS', filename) + SFX
343 unless File.exists? rcsfile
344 rcsfile.replace File.join(path, filename) + SFX
345 unless File.exists? rcsfile
346 not_found "RCS file for #{filename} in #{path}"
351 RCS.parse(filename, rcsfile)