#!/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 RcsType?(type) result = false if (type == "text" or type == "unicode" or type =="utf16" or type == "utf16" or type == "ktext" or type == "xtext" or type == "kxtext" or type == "xunicode" or type == "xutf16") result = true end return result end def GzType?(type) result = false if (type == "binary" or type == "ctext" or type == "resource" or type == "apple" or type == "cxtext" or type == "xbinary" or type == "ctempobj") result = true end return result 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)) rcsFile = File.new(rcsFile, "w") rcsFile.puts "head " + lbrRev + ";" rcsFile.puts "access ;" rcsFile.puts "symbols ;" rcsFile.puts "locks ;comment @@;" rcsFile.puts lbrRev rcsFile.puts "date " + Time.now.strftime("%Y.%m.%d.%H.%M.%S") + "; author p4; state Exp;" rcsFile.puts "branches ;" rcsFile.puts "next ;" rcsFile.puts "desc" rcsFile.puts "@@" rcsFile.puts lbrRev rcsFile.puts "log" rcsFile.puts "@@" rcsFile.puts "text" rcsFile.puts "@This is a dummy revision.@" rcsFile.close success = 1 end rescue puts "error: cannot create " + File.dirname(rcsFile) + " or " + rcsFile end return success end def CreateGzipFile(gzFile) success = 0 begin if !File.exist?(gzFile) # The binary directory may not exist File.makedirs(File.dirname(gzFile)) unless File.directory?(File.dirname(gzFile)) # create a gzip dummy revision Zlib::GzipWriter.open(gzFile) do |gz| gz.write 'This is a dummy revision.' end success = 1 end rescue puts "error: cannot create " + File.dirname(gzFile) + " or " + gzFile 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 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 gzFile = depotMap.translate(lbrFile) + ",d/" + lbrRev + ".gz" if !p4.server_case_sensitive? gzFile = gzFile.downcase end if RcsType?(lbrType) # 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 p4.run_retype("-l", "-tctext", lbrFile + "#" + lbrFileRev + ",#" + lbrFileRev) puts "info: " + lbrFile + "#" + lbrFileRev + " was fixed (" + gzFile + " was created)" else puts "error: " + lbrFile + "#" + lbrFileRev + " (" + rcsFile + ") cannot be fixed" end if File.exist?(bakFile) File.rename(bakFile, rcsFile) end elsif GzType?(lbrType) # Fix missing gzipped revision if File.exist?(gzFile) # gzipped revision exist. Corruption? begin Zlib::GzipReader.open(gzFile) { |gz| gz.read } puts "info: " + lbrFile + "#" + lbrFileRev + " was not changed (" + gzFile + ") is not corrupted" break rescue corruptedFile = gzFile + ".corrupted" File.rename(gzFile, corruptedFile) puts "info: " + lbrFile + "#" + lbrFileRev + " is corrupted" + " (" + corruptedFile + " was created)" end end success = CreateGzipFile(gzFile) 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 (" + gzFile + " was created)" else puts "error: " + lbrFile + "#" + lbrFileRev + " (" + gzFile + ") cannot be fixed" end end end end line = $<.gets end rescue P4Exception p4.errors.each { |e| $stderr.puts( e ) } raise end