#!/usr/bin/ruby ################################################################################ # # Copyright (c) 2010, Perforce Software, Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # = Description # Create dummy revisions for file revisions reported MISSING by # the "p4 verify" command. # All dummy revisions are stored as gzipped file, therefore they can be # easily replaced later on if a user has one of the missing revisions # in a workspace. # # = Usage # p4 verify -q //a_path/... | ruby dummy.rb # or # ruby dummy.rb verify_output_file # ################################################################################ require "P4" require "zlib" require "ftools" # Regular expression used for parsing the output of "p4 verify" MISSINGRexp = Regexp.new('.*(//.*)#(\d+) -.*MISSING!') # Regular expression used for detecting absolute file path ABSOLUTEPATHRexp = Regexp.new('\w:.*|^/.*') def InitialiseDepotMap(p4, p4root) depotMap = P4::Map.new() depots = p4.run_depots() depots.each do |d| path = d["map"] if !path.match( ABSOLUTEPATHRexp ) path = p4root + "/" + path end depotMap.insert( "//" + d[ "name" ].to_s + "/...", path ) end return depotMap end def StorageType(type) st = nil typeRexp = Regexp.new('(\w+)\+{0,1}(\w*)') md = type.match( typeRexp ) ftype = md[1] fmod = md[2] if (ftype != "unicode") && (ftype[0] == 117) # 117 = 'u' for ubinary st = :FULL elsif (ftype[0] == 99) # 99 = 'c' for ctext st = :GZIP elsif (ftype[0] == 108) || (ftype[1] == 108) # 108 = 'l' for ltext = text+F or xltext st = :FULL elsif fmod.index('C') != nil st = :GZIP elsif fmod.index('F') != nil st = :FULL elsif fmod.index('D') != nil st = :RCS elsif (ftype.index("tempobj") != nil) st = :FULL elsif (ftype.index("binary") != nil) || (ftype.index("resource") != nil) || (ftype.index("apple") != nil) st = :GZIP elsif (ftype.index("text") != nil) || (ftype.index("unicode") != nil) || (ftype.index("symlink") != nil) st = :RCS end return st end def CreateRCSFile(rcsFile, lbrRev) success = 0 begin # Create the missing rcs file revision only if rcs file does not exist if !File.exist?(rcsFile) # The subdirectores may not exist File.makedirs(File.dirname(rcsFile)) unless File.directory?(File.dirname(rcsFile)) f = File.new(rcsFile, "w") f.puts "head " + lbrRev + ";" f.puts "access ;" f.puts "symbols ;" f.puts "locks ;comment @@;" f.puts lbrRev f.puts "date " + Time.now.strftime("%Y.%m.%d.%H.%M.%S") + "; author p4; state Exp;" f.puts "branches ;" f.puts "next ;" f.puts "desc" f.puts "@@" f.puts lbrRev f.puts "log" f.puts "@@" f.puts "text" f.puts "@This is a dummy revision.@" f.close success = 1 end rescue puts "error: cannot create " + File.dirname(rcsFile) + " or " + rcsFile end return success end def CreateGzipFile(gzipFile) success = 0 begin if !File.exist?(gzipFile) # The binary directory may not exist File.makedirs(File.dirname(gzipFile)) unless File.directory?(File.dirname(gzipFile)) # create a gzip dummy revision Zlib::GzipWriter.open(gzipFile) do |f| f.write 'This is a dummy revision.' end success = 1 end rescue puts "error: cannot create " + File.dirname(gzipFile) + " or " + gzipFile end return success end def CreateFullFile(fullFile) success = 0 begin # The binary directory may not exist File.makedirs(File.dirname(fullFile)) unless File.directory?(File.dirname(fullFile)) # create a dummy revision f = File.new(fullFile,"w") f.puts "This is a dummy revision." f.close() success = 1 rescue puts "error: cannot create " + File.dirname(fullFile) + " or " + fullFile end return success end def FixChecksum(p4, depotFile, depotRev) p4.run_verify("-v", depotFile + "#" + depotRev + ",#" + depotRev) fstat = p4.run_fstat("-Oz", depotFile + "#" + depotRev) fstat.each do |x| depotRev = x["headRev"] lazyCopyFile = x["lazyCopyFile"] if lazyCopyFile lazyCopyRev = x["lazyCopyRev"] for i in 0...lazyCopyFile.size p4.run_verify("-v", lazyCopyFile[i] + lazyCopyRev[i] + "," + lazyCopyRev[i]) puts "info: lazy copy " + lazyCopyFile[i] + lazyCopyRev[i] + " was fixed (librarian file: " + depotFile + ")" end end end end # This function is expensive to run as it will scan # the whole db.rev table def FixChecksumSnap(p4, lbrFile, lbrRev) snap = p4.run_snap("-n", "//...", lbrFile) snap.each do |x| if lbrRev == x["lbrRev"] lazyCopyFile = x["depotFile"] lazyCopyFileRev = x["rev"] p4.run_verify("-v", lazyCopyFile + lazyCopyFileRev + "," + lazyCopyFileRev) puts "info: lazy copy " + lazyCopyFile + lazyCopyFileRev + " was fixed (librarian file: " + lbrFile + ")" end end end def LbrFileRev(p4, lbrFile, depotFile) fstat = p4.run_fstat("-Of", "-Oz", lbrFile) fstat.each do |x| depotRev = x["headRev"] lazyCopyFile = x["lazyCopyFile"] if lazyCopyFile lazyCopyRev = x["lazyCopyRev"] for i in 0...lazyCopyFile.size lazyCopyDepotFile = lazyCopyFile[i] + lazyCopyRev[i] if depotFile == lazyCopyDepotFile return depotRev end end end end return -1 end def FixRcsFile(p4, depotMap, lbrFile, lbrRev, lbrFileRev, file, rev) # Fix missing rcs revision rcsFile = depotMap.translate(lbrFile) + ",v" if !p4.server_case_sensitive? rcsFile = rcsFile.downcase end bakFile = rcsFile + ".dummy" if File.exist?(rcsFile) # missing revision in an existing rcs file File.rename(rcsFile, bakFile) end success = CreateRCSFile(rcsFile, lbrRev) if success == 1 # Force recalculation of checksum to prevent BAD verify errors if lbrFileRev == -1 if lbrFile != file # librarian depot file was obliterated and # the librarian file was manually deleted lbrFileRev = rev FixChecksumSnap(p4, lbrFile, lbrRev) lbrFile = file elsif p4.run_verify("-v", file + "#" + rev + ",#" + rev) end else FixChecksum(p4, lbrFile, lbrFileRev) end # Convert rcs file revision to gz file revision gzipFile = depotMap.translate(lbrFile) + ",d/" + lbrRev + ".gz" if !p4.server_case_sensitive? gzipFile = gzipFile.downcase end p4.run_retype("-l", "-tctext", lbrFile + "#" + lbrFileRev + ",#" + lbrFileRev) puts "info: " + lbrFile + "#" + lbrFileRev + " was fixed (" + gzipFile + " was created)" else puts "error: " + lbrFile + "#" + lbrFileRev + " (" + rcsFile + ") cannot be fixed" end if File.exist?(bakFile) File.rename(bakFile, rcsFile) end end def FixGzipFile(p4, depotMap, lbrFile, lbrRev, lbrFileRev, file, rev) gzipFile = depotMap.translate(lbrFile) + ",d/" + lbrRev + ".gz" if !p4.server_case_sensitive? gzipFile = gzipFile.downcase end if File.exist?(gzipFile) # gzipped revision exist. Corruption? begin Zlib::GzipReader.open(gzipFile) { |gz| gz.read } puts "info: " + lbrFile + "#" + lbrFileRev + " was not changed (" + gzipFile + ") is not corrupted" break rescue corruptedFile = gzipFile + ".corrupted" File.rename(gzipFile, corruptedFile) puts "info: " + lbrFile + "#" + lbrFileRev + " is corrupted" + " (" + corruptedFile + " was created)" end end success = CreateGzipFile(gzipFile) if success == 1 # Force recalculation of checksum to prevent BAD verify errors if lbrFileRev == -1 if lbrFile != file # librarian depot file was obliterated and # the librarian file was manually deleted lbrFile = file lbrFileRev = rev FixChecksumSnap(p4, lbrFile, lbrRev) elsif p4.run_verify("-v", file + "#" + rev + ",#" + rev) end else FixChecksum(p4, lbrFile, lbrFileRev) end puts "info: " + lbrFile + "#" + lbrFileRev + " was fixed (" + gzipFile + " was created)" else puts "error: " + lbrFile + "#" + lbrFileRev + " (" + gzipFile + ") cannot be fixed" end end def FixFullFile(p4, depotMap, lbrFile, lbrRev, lbrFileRev, file, rev) fullFile = depotMap.translate(lbrFile) + ",d/" + lbrRev if !p4.server_case_sensitive? fullFile = fullFile.downcase end success = CreateFullFile(fullFile) if success == 1 # Force recalculation of checksum to prevent BAD verify errors if lbrFileRev == -1 if lbrFile != file # librarian depot file was obliterated and # the librarian file was manually deleted lbrFile = file lbrFileRev = rev FixChecksumSnap(p4, lbrFile, lbrRev) elsif p4.run_verify("-v", file + "#" + rev + ",#" + rev) end else FixChecksum(p4, lbrFile, lbrFileRev) end puts "info: " + lbrFile + "#" + lbrFileRev + " was fixed (" + fullFile + " was created)" else puts "error: " + lbrFile + "#" + lbrFileRev + " (" + fullFile + ") cannot be fixed" end end begin version = P4::VERSION if version.to_f < 2009.2 puts("error: P4Ruby 2009.2 or greater is required") exit(0) end p4 = P4.new p4.exception_level = P4::RAISE_ERRORS p4.connect p4info = p4.run_info().shift p4root = p4info [ "serverRoot" ] if (p4.server_level < 23) puts "error: a Perforce server 2007.2 or greater is required" exit(0) elsif (!p4root.match( ABSOLUTEPATHRexp )) puts("error: P4ROOT directroy must be set with an absolute path") exit(0) end depotMap = InitialiseDepotMap(p4, p4root) line = $<.gets while line if (md = MISSINGRexp.match(line)) file = md[1] rev = md[2] verify = p4.run_verify("-q", file + "#" + rev + ",#" + rev).shift if verify && verify["status"].match("MISSING!") success = 0 fstat = p4.run_fstat("-Oc", file + "#" + rev).shift lbrFile = fstat[ "lbrFile" ] lbrRev = fstat[ "lbrRev" ] lbrType = fstat[ "lbrType" ] # get the revision of the librarian file if any lbrFileRev = LbrFileRev(p4, lbrFile, file +"#" + rev) if lbrFileRev == -1 && lbrFile == file lbrFileRev = rev end st = StorageType(lbrType) if st == :RCS FixRcsFile(p4, depotMap, lbrFile, lbrRev, lbrFileRev, file, rev) elsif st == :GZIP # Fix missing gzipped revision FixGzipFile(p4, depotMap, lbrFile, lbrRev, lbrFileRev, file, rev) elsif st == :FULL # Fix missing full revision FixFullFile(p4, depotMap, lbrFile, lbrRev, lbrFileRev, file, rev) end end end line = $<.gets end rescue P4Exception p4.errors.each { |e| $stderr.puts( e ) } raise end