script to retroactively add commitids to past openbsd commits

move classes to separate files

+327 -321
+9 -321
openbsd-commitid.rb
··· 10 10 # add commitids back to rcs files 11 11 # $ ruby openbsd-commitid.rb 12 12 # 13 + # NOTE: rcs/rlog must be modified to end revisions with ###, not just a line of 14 + # dashes since those appear in some commit messages 15 + # 16 + 17 + $:.push "." 18 + 19 + require "scanner" 20 + require "rcsfile" 21 + require "rcsrevision" 13 22 14 23 CVSROOT = "/var/cvs-commitid/" 15 24 CVSTMP = "/var/cvs-tmp/" ··· 35 44 # stored back in CVSROOT/#{tree} 36 45 sc.repo_surgery(CVSTMP, CVSROOT, tree) 37 46 end 38 - 39 - BEGIN { 40 - require "sqlite3" 41 - 42 - class RCSFile 43 - attr_accessor :revisions 44 - 45 - def initialize(file) 46 - @revisions = {} 47 - 48 - # rcs modified to end revs in ### 49 - blocks = [] 50 - IO.popen([ "rlog", file ]) do |rlog| 51 - blocks = rlog.read.force_encoding("binary"). 52 - split(/^(-{28}|={77})###\n?$/).reject{|b| b.match(/^(-{28}|={77})$/) } 53 - end 54 - 55 - if !blocks.first.match(/^RCS file/) 56 - raise "file #{file} didn't come out of rlog properly" 57 - end 58 - 59 - blocks.shift 60 - blocks.each do |block| 61 - rev = RCSRevision.new(block) 62 - if @revisions[rev.revision] 63 - raise "duplicate revision #{rev.revision} in #{file}" 64 - end 65 - @revisions[rev.revision] = rev 66 - end 67 - end 68 - end 69 - 70 - class RCSRevision 71 - attr_accessor :revision, :date, :author, :state, :lines, :commitid, :log 72 - 73 - # str: "revision 1.7\ndate: 1996/12/14 12:17:33; author: mickey; state: Exp; lines: +3 -3;\n-Wall'ing." 74 - def initialize(str) 75 - @revision = nil 76 - @date = 0 77 - @author = nil 78 - @state = nil 79 - @lines = nil 80 - @commitid = nil 81 - @log = nil 82 - 83 - lines = str.gsub(/^\s*/, "").split("\n") 84 - # -> [ 85 - # "revision 1.7", 86 - # "date: 1996/12/14 12:17:33; author: mickey; state: Exp; lines: +3 -3;", 87 - # "-Wall'ing." 88 - # ] 89 - 90 - # strip out possible branches line in log 91 - if lines[2].to_s.match(/^branches:\s+([\d\.]+)/) 92 - lines.delete_at(2) 93 - end 94 - 95 - @revision = lines.first.scan(/^revision ([\d\.]+)($|\tlocked by)/).first.first 96 - # -> "1.7" 97 - 98 - # date/author/state/lines/commitid line 99 - lines[1].split(/;[ \t]*/).each do |piece| 100 - kv = piece.split(": ") 101 - self.send(kv[0] + "=", kv[1]) 102 - end 103 - # -> @date = "1996/12/14 12:17:33", @author = "mickey", ... 104 - 105 - if m = @date.match(/^\d\d\d\d\/\d\d\/\d\d \d\d:\d\d:\d\d$/) 106 - @date = DateTime.parse(@date).strftime("%s").to_i 107 - else 108 - raise "invalid date #{@date}" 109 - end 110 - # -> @date = 850565853 111 - 112 - @log = lines[2, lines.count].join("\n") 113 - end 114 - end 115 - 116 - class Scanner 117 - def initialize(dbf, root) 118 - @db = SQLite3::Database.new dbf 119 - 120 - @db.execute "CREATE TABLE IF NOT EXISTS changesets 121 - (id integer primary key, date integer, author text, commitid text, 122 - log text)" 123 - @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS u_commitid ON changesets 124 - (commitid)" 125 - 126 - @db.execute "CREATE TABLE IF NOT EXISTS files 127 - (id integer primary key, file text)" 128 - @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS u_file ON files 129 - (file)" 130 - 131 - @db.execute "CREATE TABLE IF NOT EXISTS revisions 132 - (id integer primary key, file_id integer, changeset_id integer, 133 - date integer, version text, author text, commitid text, log text)" 134 - @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS u_revision ON revisions 135 - (file_id, version)" 136 - @db.execute "CREATE INDEX IF NOT EXISTS empty_changesets ON revisions 137 - (changeset_id)" 138 - @db.execute "CREATE INDEX IF NOT EXISTS cs_by_commitid ON revisions 139 - (commitid, changeset_id)" 140 - @db.execute "CREATE INDEX IF NOT EXISTS all_revs_by_author ON revisions 141 - (author, date)" 142 - 143 - @db.results_as_hash = true 144 - 145 - @root = (root + "/").gsub(/\/\//, "/") 146 - end 147 - 148 - def recursively_scan(dir = nil) 149 - if !dir 150 - dir = @root 151 - end 152 - 153 - puts "recursing into #{dir}" 154 - 155 - Dir.glob((dir + "/*").gsub(/\/\//, "/")).each do |f| 156 - if Dir.exists?(f) 157 - recursively_scan(f) 158 - elsif f.match(/,v$/) 159 - scan(f) 160 - end 161 - end 162 - end 163 - 164 - def scan(f) 165 - canfile = f[@root.length, f.length - @root.length].gsub(/\/Attic\//, "/") 166 - puts " scanning file #{canfile}" 167 - 168 - rcs = RCSFile.new(f) 169 - 170 - fid = @db.execute("SELECT id FROM files WHERE file = ?", [ canfile ]).first 171 - if !fid 172 - @db.execute("INSERT INTO files (file) VALUES (?)", [ canfile ]) 173 - fid = @db.execute("SELECT id FROM files WHERE file = ?", 174 - [ canfile ]).first 175 - end 176 - raise if !fid 177 - 178 - rcs.revisions.each do |r,rev| 179 - rid = @db.execute("SELECT id, commitid FROM revisions WHERE " + 180 - "file_id = ? AND version = ?", [ fid["id"], r ]).first 181 - 182 - if rid 183 - if rid["commitid"] != rev.commitid 184 - puts " updated #{r} to commitid #{rev.commitid}" + 185 - (rid["commitid"].to_s == "" ? "" : " from #{rid["commitid"]}") 186 - 187 - @db.execute("UPDATE revisions SET commitid = ? WHERE file_id = ? " + 188 - "AND version = ?", [ rev.commitid, fid["id"], rev.revision ]) 189 - end 190 - else 191 - puts " inserted #{r}, authored #{rev.date} by #{rev.author}" + 192 - (rev.commitid ? ", commitid #{rev.commitid}" : "") 193 - 194 - @db.execute("INSERT INTO revisions (file_id, date, version, author, " + 195 - "commitid, log) VALUES (?, ?, ?, ?, ?, ?)", [ fid["id"], rev.date, 196 - rev.revision, rev.author, rev.commitid, rev.log ]) 197 - end 198 - end 199 - end 200 - 201 - def stray_commitids_to_changesets 202 - stray_commitids = @db.execute("SELECT DISTINCT author, commitid FROM " + 203 - "revisions WHERE commitid IS NOT NULL AND changeset_id IS NULL") 204 - stray_commitids.each do |row| 205 - csid = @db.execute("SELECT id FROM changesets WHERE commitid = ?", 206 - [ row["commitid"] ]).first 207 - if !csid 208 - @db.execute("INSERT INTO changesets (author, commitid) VALUES (?, ?)", 209 - [ row["author"], row["commitid"] ]) 210 - csid = @db.execute("SELECT id FROM changesets WHERE commitid = ?", 211 - [ row["commitid"] ]).first 212 - end 213 - raise if !csid 214 - 215 - puts "commitid #{row["commitid"]} -> changeset #{csid["id"]}" 216 - 217 - @db.execute("UPDATE revisions SET changeset_id = ? WHERE commitid = ?", 218 - [ csid["id"], row["commitid"] ]) 219 - end 220 - end 221 - 222 - def group_into_changesets 223 - new_sets = [] 224 - last_row = {} 225 - cur_set = [] 226 - 227 - @db.execute("SELECT * FROM revisions WHERE changeset_id IS NULL ORDER " + 228 - "BY author ASC, date ASC") do |row| 229 - # commits by the same author with the same log message (unless they're 230 - # initial imports - 1.1.1.1) within 30 seconds of each other are grouped 231 - # together 232 - if last_row.any? && row["author"] == last_row["author"] && 233 - (row["log"] == last_row["log"] || row["log"] == "Initial revision" || 234 - last_row["log"] == "Initial revision") && 235 - row["date"].to_i - last_row["date"].to_i <= 30 236 - cur_set.push row["id"].to_i 237 - elsif !last_row.any? 238 - cur_set.push row["id"].to_i 239 - else 240 - if cur_set.any? 241 - new_sets.push cur_set 242 - cur_set = [] 243 - end 244 - cur_set.push row["id"].to_i 245 - end 246 - 247 - last_row = row 248 - end 249 - 250 - if cur_set.any? 251 - new_sets.push cur_set 252 - end 253 - 254 - new_sets.each do |s| 255 - puts "new set with revision ids #{s.inspect}" 256 - @db.execute("INSERT INTO changesets (id) VALUES (NULL)") 257 - id = @db.execute("SELECT last_insert_rowid() AS id").first["id"] 258 - raise if !id 259 - 260 - # avoid an exception caused by passing too many variables 261 - s.each_slice(100) do |chunk| 262 - @db.execute("UPDATE revisions SET changeset_id = ? WHERE id IN (" + 263 - chunk.map{|a| "?" }.join(",") + ")", [ id ] + chunk) 264 - end 265 - end 266 - 267 - if @db.execute("SELECT * FROM revisions WHERE changeset_id IS NULL").any? 268 - raise "still have revisions with empty changesets" 269 - end 270 - end 271 - 272 - def fill_in_changeset_data 273 - cses = {} 274 - @db.execute("SELECT id, commitid FROM changesets WHERE date IS NULL") do |c| 275 - cses[c["id"]] = c["commitid"] 276 - end 277 - 278 - cses.each do |csid,comid| 279 - date = nil 280 - commitid = comid 281 - log = nil 282 - author = nil 283 - 284 - @db.execute("SELECT * FROM revisions WHERE changeset_id = ? ORDER BY " + 285 - "date ASC", [ csid ]) do |rev| 286 - if !date 287 - date = rev["date"] 288 - end 289 - 290 - if rev["log"] != "Initial revision" 291 - log = rev["log"] 292 - end 293 - 294 - if author && rev["author"] != author 295 - raise "authors different between revs of #{csid}" 296 - else 297 - author = rev["author"] 298 - end 299 - end 300 - 301 - if commitid.to_s == "" 302 - commitid = "" 303 - while commitid.length < 16 304 - c = rand(75) + 48 305 - if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || 306 - (c >= 97 && c <= 122)) 307 - commitid << c.chr 308 - end 309 - end 310 - end 311 - 312 - if !date 313 - raise "no date for changeset #{csid}" 314 - end 315 - 316 - puts "changeset #{csid} -> commitid #{commitid}" 317 - 318 - @db.execute("UPDATE changesets SET date = ?, commitid = ?, log = ?, " + 319 - "author = ? WHERE id = ?", [ date, commitid, log, author, csid ]) 320 - end 321 - end 322 - 323 - def repo_surgery(tmp_dir, cvs_root, tree) 324 - puts "checking out repo \"#{tree}\" to #{tmp_dir}" 325 - 326 - Dir.chdir(tmp_dir) 327 - # don't pass -P because we'll need empty dirs around for Attic changes 328 - system("cvs", "-Q", "-d", cvs_root, "co", tree) 329 - 330 - Dir.chdir(tmp_dir + "/#{tree}") 331 - 332 - csid = nil 333 - @db.execute("SELECT 334 - files.file, changesets.commitid, changesets.author, changesets.date, 335 - revisions.version 336 - FROM revisions 337 - LEFT OUTER JOIN files ON files.id = file_id 338 - LEFT OUTER JOIN changesets ON revisions.changeset_id = changesets.id 339 - WHERE revisions.commitid IS NULL 340 - ORDER BY changesets.date ASC, files.file ASC") do |rev| 341 - if csid == nil || rev["commitid"] != csid 342 - puts " commit #{rev["commitid"]} at #{Time.at(rev["date"])} by " + 343 - rev["author"] 344 - csid = rev["commitid"] 345 - end 346 - 347 - puts " #{rev["file"]} #{rev["version"]}" 348 - 349 - system("cvs", "-Q", "admin", "-C", 350 - "#{rev["version"]}:#{rev["commitid"]}", rev["file"].gsub(/,v$/, "")) 351 - end 352 - 353 - puts "cleaning up #{tmp_dir}/#{tree}" 354 - 355 - system("rm", "-rf", tmp_dir + "/#{tree}") 356 - end 357 - end 358 - }
+27
rcsfile.rb
··· 1 + class RCSFile 2 + attr_accessor :revisions 3 + 4 + def initialize(file) 5 + @revisions = {} 6 + 7 + # rcs modified to end revs in ### 8 + blocks = [] 9 + IO.popen([ "rlog", file ]) do |rlog| 10 + blocks = rlog.read.force_encoding("binary"). 11 + split(/^(-{28}|={77})###\n?$/).reject{|b| b.match(/^(-{28}|={77})$/) } 12 + end 13 + 14 + if !blocks.first.match(/^RCS file/) 15 + raise "file #{file} didn't come out of rlog properly" 16 + end 17 + 18 + blocks.shift 19 + blocks.each do |block| 20 + rev = RCSRevision.new(block) 21 + if @revisions[rev.revision] 22 + raise "duplicate revision #{rev.revision} in #{file}" 23 + end 24 + @revisions[rev.revision] = rev 25 + end 26 + end 27 + end
+47
rcsrevision.rb
··· 1 + require "date" 2 + 3 + class RCSRevision 4 + attr_accessor :revision, :date, :author, :state, :lines, :commitid, :log 5 + 6 + # str: "revision 1.7\ndate: 1996/12/14 12:17:33; author: mickey; state: Exp; lines: +3 -3;\n-Wall'ing." 7 + def initialize(str) 8 + @revision = nil 9 + @date = 0 10 + @author = nil 11 + @state = nil 12 + @lines = nil 13 + @commitid = nil 14 + @log = nil 15 + 16 + lines = str.gsub(/^\s*/, "").split("\n") 17 + # -> [ 18 + # "revision 1.7", 19 + # "date: 1996/12/14 12:17:33; author: mickey; state: Exp; lines: +3 -3;", 20 + # "-Wall'ing." 21 + # ] 22 + 23 + # strip out possible branches line in log 24 + if lines[2].to_s.match(/^branches:\s+([\d\.]+)/) 25 + lines.delete_at(2) 26 + end 27 + 28 + @revision = lines.first.scan(/^revision ([\d\.]+)($|\tlocked by)/).first.first 29 + # -> "1.7" 30 + 31 + # date/author/state/lines/commitid line 32 + lines[1].split(/;[ \t]*/).each do |piece| 33 + kv = piece.split(": ") 34 + self.send(kv[0] + "=", kv[1]) 35 + end 36 + # -> @date = "1996/12/14 12:17:33", @author = "mickey", ... 37 + 38 + if m = @date.match(/^\d\d\d\d\/\d\d\/\d\d \d\d:\d\d:\d\d$/) 39 + @date = DateTime.parse(@date).strftime("%s").to_i 40 + else 41 + raise "invalid date #{@date}" 42 + end 43 + # -> @date = 850565853 44 + 45 + @log = lines[2, lines.count].join("\n") 46 + end 47 + end
+244
scanner.rb
··· 1 + require "sqlite3" 2 + 3 + class Scanner 4 + def initialize(dbf, root) 5 + @db = SQLite3::Database.new dbf 6 + 7 + @db.execute "CREATE TABLE IF NOT EXISTS changesets 8 + (id integer primary key, date integer, author text, commitid text, 9 + log text)" 10 + @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS u_commitid ON changesets 11 + (commitid)" 12 + 13 + @db.execute "CREATE TABLE IF NOT EXISTS files 14 + (id integer primary key, file text)" 15 + @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS u_file ON files 16 + (file)" 17 + 18 + @db.execute "CREATE TABLE IF NOT EXISTS revisions 19 + (id integer primary key, file_id integer, changeset_id integer, 20 + date integer, version text, author text, commitid text, log text)" 21 + @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS u_revision ON revisions 22 + (file_id, version)" 23 + @db.execute "CREATE INDEX IF NOT EXISTS empty_changesets ON revisions 24 + (changeset_id)" 25 + @db.execute "CREATE INDEX IF NOT EXISTS cs_by_commitid ON revisions 26 + (commitid, changeset_id)" 27 + @db.execute "CREATE INDEX IF NOT EXISTS all_revs_by_author ON revisions 28 + (author, date)" 29 + 30 + @db.results_as_hash = true 31 + 32 + @root = (root + "/").gsub(/\/\//, "/") 33 + end 34 + 35 + def recursively_scan(dir = nil) 36 + if !dir 37 + dir = @root 38 + end 39 + 40 + puts "recursing into #{dir}" 41 + 42 + Dir.glob((dir + "/*").gsub(/\/\//, "/")).each do |f| 43 + if Dir.exists?(f) 44 + recursively_scan(f) 45 + elsif f.match(/,v$/) 46 + scan(f) 47 + end 48 + end 49 + end 50 + 51 + def scan(f) 52 + canfile = f[@root.length, f.length - @root.length].gsub(/\/Attic\//, "/") 53 + puts " scanning file #{canfile}" 54 + 55 + rcs = RCSFile.new(f) 56 + 57 + fid = @db.execute("SELECT id FROM files WHERE file = ?", [ canfile ]).first 58 + if !fid 59 + @db.execute("INSERT INTO files (file) VALUES (?)", [ canfile ]) 60 + fid = @db.execute("SELECT id FROM files WHERE file = ?", 61 + [ canfile ]).first 62 + end 63 + raise if !fid 64 + 65 + rcs.revisions.each do |r,rev| 66 + rid = @db.execute("SELECT id, commitid FROM revisions WHERE " + 67 + "file_id = ? AND version = ?", [ fid["id"], r ]).first 68 + 69 + if rid 70 + if rid["commitid"] != rev.commitid 71 + puts " updated #{r} to commitid #{rev.commitid}" + 72 + (rid["commitid"].to_s == "" ? "" : " from #{rid["commitid"]}") 73 + 74 + @db.execute("UPDATE revisions SET commitid = ? WHERE file_id = ? " + 75 + "AND version = ?", [ rev.commitid, fid["id"], rev.revision ]) 76 + end 77 + else 78 + puts " inserted #{r}, authored #{rev.date} by #{rev.author}" + 79 + (rev.commitid ? ", commitid #{rev.commitid}" : "") 80 + 81 + @db.execute("INSERT INTO revisions (file_id, date, version, author, " + 82 + "commitid, log) VALUES (?, ?, ?, ?, ?, ?)", [ fid["id"], rev.date, 83 + rev.revision, rev.author, rev.commitid, rev.log ]) 84 + end 85 + end 86 + end 87 + 88 + def stray_commitids_to_changesets 89 + stray_commitids = @db.execute("SELECT DISTINCT author, commitid FROM " + 90 + "revisions WHERE commitid IS NOT NULL AND changeset_id IS NULL") 91 + stray_commitids.each do |row| 92 + csid = @db.execute("SELECT id FROM changesets WHERE commitid = ?", 93 + [ row["commitid"] ]).first 94 + if !csid 95 + @db.execute("INSERT INTO changesets (author, commitid) VALUES (?, ?)", 96 + [ row["author"], row["commitid"] ]) 97 + csid = @db.execute("SELECT id FROM changesets WHERE commitid = ?", 98 + [ row["commitid"] ]).first 99 + end 100 + raise if !csid 101 + 102 + puts "commitid #{row["commitid"]} -> changeset #{csid["id"]}" 103 + 104 + @db.execute("UPDATE revisions SET changeset_id = ? WHERE commitid = ?", 105 + [ csid["id"], row["commitid"] ]) 106 + end 107 + end 108 + 109 + def group_into_changesets 110 + new_sets = [] 111 + last_row = {} 112 + cur_set = [] 113 + 114 + @db.execute("SELECT * FROM revisions WHERE changeset_id IS NULL ORDER " + 115 + "BY author ASC, date ASC") do |row| 116 + # commits by the same author with the same log message (unless they're 117 + # initial imports - 1.1.1.1) within 30 seconds of each other are grouped 118 + # together 119 + if last_row.any? && row["author"] == last_row["author"] && 120 + (row["log"] == last_row["log"] || row["log"] == "Initial revision" || 121 + last_row["log"] == "Initial revision") && 122 + row["date"].to_i - last_row["date"].to_i <= 30 123 + cur_set.push row["id"].to_i 124 + elsif !last_row.any? 125 + cur_set.push row["id"].to_i 126 + else 127 + if cur_set.any? 128 + new_sets.push cur_set 129 + cur_set = [] 130 + end 131 + cur_set.push row["id"].to_i 132 + end 133 + 134 + last_row = row 135 + end 136 + 137 + if cur_set.any? 138 + new_sets.push cur_set 139 + end 140 + 141 + new_sets.each do |s| 142 + puts "new set with revision ids #{s.inspect}" 143 + @db.execute("INSERT INTO changesets (id) VALUES (NULL)") 144 + id = @db.execute("SELECT last_insert_rowid() AS id").first["id"] 145 + raise if !id 146 + 147 + # avoid an exception caused by passing too many variables 148 + s.each_slice(100) do |chunk| 149 + @db.execute("UPDATE revisions SET changeset_id = ? WHERE id IN (" + 150 + chunk.map{|a| "?" }.join(",") + ")", [ id ] + chunk) 151 + end 152 + end 153 + 154 + if @db.execute("SELECT * FROM revisions WHERE changeset_id IS NULL").any? 155 + raise "still have revisions with empty changesets" 156 + end 157 + end 158 + 159 + def fill_in_changeset_data 160 + cses = {} 161 + @db.execute("SELECT id, commitid FROM changesets WHERE date IS NULL") do |c| 162 + cses[c["id"]] = c["commitid"] 163 + end 164 + 165 + cses.each do |csid,comid| 166 + date = nil 167 + commitid = comid 168 + log = nil 169 + author = nil 170 + 171 + @db.execute("SELECT * FROM revisions WHERE changeset_id = ? ORDER BY " + 172 + "date ASC", [ csid ]) do |rev| 173 + if !date 174 + date = rev["date"] 175 + end 176 + 177 + if rev["log"] != "Initial revision" 178 + log = rev["log"] 179 + end 180 + 181 + if author && rev["author"] != author 182 + raise "authors different between revs of #{csid}" 183 + else 184 + author = rev["author"] 185 + end 186 + end 187 + 188 + if commitid.to_s == "" 189 + commitid = "" 190 + while commitid.length < 16 191 + c = rand(75) + 48 192 + if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || 193 + (c >= 97 && c <= 122)) 194 + commitid << c.chr 195 + end 196 + end 197 + end 198 + 199 + if !date 200 + raise "no date for changeset #{csid}" 201 + end 202 + 203 + puts "changeset #{csid} -> commitid #{commitid}" 204 + 205 + @db.execute("UPDATE changesets SET date = ?, commitid = ?, log = ?, " + 206 + "author = ? WHERE id = ?", [ date, commitid, log, author, csid ]) 207 + end 208 + end 209 + 210 + def repo_surgery(tmp_dir, cvs_root, tree) 211 + puts "checking out repo \"#{tree}\" to #{tmp_dir}" 212 + 213 + Dir.chdir(tmp_dir) 214 + # don't pass -P because we'll need empty dirs around for Attic changes 215 + system("cvs", "-Q", "-d", cvs_root, "co", tree) 216 + 217 + Dir.chdir(tmp_dir + "/#{tree}") 218 + 219 + csid = nil 220 + @db.execute("SELECT 221 + files.file, changesets.commitid, changesets.author, changesets.date, 222 + revisions.version 223 + FROM revisions 224 + LEFT OUTER JOIN files ON files.id = file_id 225 + LEFT OUTER JOIN changesets ON revisions.changeset_id = changesets.id 226 + WHERE revisions.commitid IS NULL 227 + ORDER BY changesets.date ASC, files.file ASC") do |rev| 228 + if csid == nil || rev["commitid"] != csid 229 + puts " commit #{rev["commitid"]} at #{Time.at(rev["date"])} by " + 230 + rev["author"] 231 + csid = rev["commitid"] 232 + end 233 + 234 + puts " #{rev["file"]} #{rev["version"]}" 235 + 236 + system("cvs", "-Q", "admin", "-C", 237 + "#{rev["version"]}:#{rev["commitid"]}", rev["file"].gsub(/,v$/, "")) 238 + end 239 + 240 + puts "cleaning up #{tmp_dir}/#{tree}" 241 + 242 + system("rm", "-rf", tmp_dir + "/#{tree}") 243 + end 244 + end