#!/usr/bin/env python import p4helpers import sys import os import shutil import pickle import zipfile import zlib import datetime import subprocess import getopt import glob import struct try: import wx except: pass # # Set program defaults. # g_ChangelistInfoFilename = '__CHANGELISTINFO__' RUNMODE_BACKUP = 0 RUNMODE_RESTORE = 1 RUNMODE_EMPTY = 2 RUNMODE_NONE = 3 g_RunMode = RUNMODE_NONE g_ZipFilename = '' g_bAutoRevert = False g_bBinsOnly = False g_Changelist = [] g_bShowUI = False g_bPickleAllChanges = False g_bVerbose = True g_IncludeGlobPatterns = [] g_ExcludeGlobPatterns = [] g_ChangelistComment = '' g_DefaultFilename = None g_bCreateDiff = False g_DiffDir = '' g_SettingsFilename = 'vcodepickle.cfg' g_Settings = {} g_UISaveDirKey = 'uisavedir' # These are used with -changetree if the user wants to restore the changelist to # a different tree than it originally came from. g_FromTree = None g_ToTree = None g_QueuedWarnings = [] # # Pickle data for various file versions supported by vcodepickle. # class CV2Data: changelistComment = None class CV3Data: relativeToDepotFileMap = None def ShowUsage(): print "vcodepickle.py [options] <--backup [options] or --restore or --diff or --empty> [--file filename] [--changelist | --bins]" print "" print "-q/--quiet sshhhhhh." print "-b/--backup pickle local changes to file" print "-r/--restore apply pickled edits to client" print "--empty create a valid, but empty, pickle, used by buildbot" print "-d/--diff produce a diff of the changes in a vcodepickled zipfile" print "-f/--file filename pickle file - optional for backup, required for restore" print "-c/--changelist <#|all> in backup mode, pickle specified changelist(s)" print " in restore mode, reapply changes to the specified changelist" print " defaults to 'default'" print " --prompt if using --backup, this asks where to backup (and makes suggestions)" print " if using --restore, this brings up a file open dialog" print "" print "backup options:" print "" print " --bins pickle binaries from the auto-checkout changelist" print " --revert in backup mode, revert open files after pickling" print " -e/--exclude a glob pattern of files to exclude from pickling" print " -i/--include a glob pattern of files to include in the code pickle" print "" print "restore options:" print "" print " --changetree allows you to restore to a Perforce tree different from the original one" print "" print "diff options:" print "" print " --diffdir specify where the diff files will go" print "" print 'ex: vcodepickle.py --backup --changelist all --exclude "bin/*"' print ' creates a zip file of all files not matching "bin/*" in all changelists' print "" print "ex: vcodepickle.py --restore --file vcodepickle_2009_01_20.zip -c 342421" print " restore from the specified file into changelist 342421" print "" print "ex: vcodepickle.py --diff --file VCP_2009-03-26__17h-01m-25s.zip" print " creates a diff of the pickled changes in the specified file" sys.exit( 1 ) def DefaultPickleDirName( username ): return '\\\\fileserver\\user\\' + username + '\\vcodepickles' def DefaultPickleDiffName( username ): return '\\\\fileserver\\user\\' + username + '\\vcodepicklediff' def CreateChangelist( comment ): po = subprocess.Popen( 'p4 change -i', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE ) po.stdin.write( 'Change: new\n\nDescription:\n\t' + comment.replace('\n','\n\t') + '\n\n' ) po.stdin.close() theOutput = po.stdout.readline() ret = po.wait() if ret == 0: return int( theOutput.split( ' ' )[1] ) else: print "Creating a changelist failed." print theOutput sys.exit( 1 ) def GetChangelistComment( nChangelist ): po = subprocess.Popen( 'p4 change -o ' + nChangelist, shell=True, stdout=subprocess.PIPE ) lines = po.stdout.readlines() ret = po.wait() if ret != 0: print "Couldn't get changelist description for change " + nChangelist sys.exit( 1 ) comment = '' bFound = False for line in lines: if bFound == True: if line[0] == '\t': comment += line[1:] else: break else: if line[:12] == 'Description:': bFound = True return comment def chomp( str ): while 1: str2 = str.lstrip( '\n' ).lstrip( '\r' ).rstrip( '\n' ).rstrip( '\r' ) if len( str2 ) == len( str ): return str else: str = str2 def AddQueuedWarning( s ): global g_QueuedWarnings g_QueuedWarnings.append( s ) def PrintQueuedWarnings(): global g_QueuedWarnings if len( g_QueuedWarnings ) > 0: print "\n\n===== WARNINGS =====\n" nWarning = 1 for warning in g_QueuedWarnings: print "%d:\n%s\n" % (nWarning, warning) nWarning += 1 def DoesFileExistInPerforce( filename ): x = p4helpers.ReadPerforceOutput( 'p4 -G fstat ' + filename ) return (x[1][0]['code'] != 'error') # # The settings file is just a bunch of key/value pairs on different lines separated by = # def LoadSettingsFile( filename ): ret = {} try: f = open( filename, 'rt' ) except IOError: return {} for line in f.readlines(): line = chomp( line ) parts = line.split( '=' ) if len( parts ) > 0: ret[parts[0]] = parts[1] f.close() return ret; def SaveSettingsFile( filename, thedict ): f = open( filename, 'wt' ) for (k,v) in thedict.items(): f.write( '%s=%s\n' % (k, v) ) f.close() def SetZipFilename( filename ): global g_ZipFilename g_ZipFilename = filename if g_ZipFilename[-4:].lower() != '.zip': g_ZipFilename += '.zip' def GetReadableChangelistComment( str ): alphabet = 'abcdefghijklmnopqrstuvwxyz' # Strip out all lists of repeating nonalphabetic characters tempstr = '' prevchar = '' for i in range(0,len(str)): testchar = str[i:i+1] if testchar != prevchar or alphabet.find(testchar) != -1: tempstr += testchar prevchar = testchar str = tempstr str = str[:100] outstr = '' validchars = alphabet + alphabet.upper() + '0123456789_' prevchar = '' for i in range(0,len(str)): testchar = str[i:i+1] if validchars.find( testchar ) == -1: if prevchar != '-': outstr += '-' else: outstr += testchar prevchar = outstr[-1:] return outstr def ShowUIForFilename(): global g_Settings global g_SettingsFilename global g_ZipFilename global g_DefaultFilename # # Get the directory to save to. # username = os.environ['username'] defaultdirname = DefaultPickleDirName(username) if not g_Settings.has_key( g_UISaveDirKey ): g_Settings[g_UISaveDirKey] = defaultdirname if g_Settings[g_UISaveDirKey] == 'default': theinput = raw_input( '\nDirectory\n=========\n(Leave blank for "%s")\n> ' % ( defaultdirname ) ) else: theinput = raw_input( '\nDirectory\n=========\n(Leave blank for "%s")\n(Type "default" for "%s"\n--> ' % ( g_Settings[g_UISaveDirKey], defaultdirname ) ) if theinput == '': theDirName = g_Settings[g_UISaveDirKey] else: g_Settings[g_UISaveDirKey] = theinput theDirName = theinput if theDirName.lower() == 'default': theDirName = defaultdirname # # Make sure it exists.. create it if necessary. # if os.access( theDirName, os.F_OK ) != True: theinput = raw_input( "Directory %s doesn't exist. Create? (Y/n) " % theDirName ) if theinput == '' or theinput.lower() == 'y': os.makedirs( theDirName ) else: print "Exiting." sys.exit( 1 ) # # Get the filename to save to. # theinput = raw_input( '\nFilename\n========\n(Leave blank for "%s")\n> ' % ( g_DefaultFilename ) ) if theinput == '': theZipFilename = g_DefaultFilename else: theZipFilename = theinput # # Construct the actual zip filename we'll use. # SetZipFilename( os.path.join( theDirName, theZipFilename ) ) # # Check if it already exists. # if os.access( g_ZipFilename, os.F_OK ) == True: theinput = raw_input( "%s already exists. Overwrite? (y/N)" % g_ZipFilename ) if theinput == '' or theinput.lower() == 'n': print "Exiting." sys.exit(1) # # Save the settings back out. # SaveSettingsFile( g_SettingsFilename, g_Settings ) class VCodePickle: theZipFile = None filesDeleted = {} filesAdded = {} filesEdited = {} changelistComment = '' relativeToDepotFileMap = {} # Maps src\engine\cl_main.cpp to //valvegames/main/src/engine/cl_main.cpp (obviously //valvegames/main/src changes based on the root) def Load( self, zipFilename ): self.theZipFile = zipfile.ZipFile( zipFilename, 'r', zipfile.ZIP_DEFLATED ) # Load the added and deleted lists. changelistinfo = self.theZipFile.read( g_ChangelistInfoFilename + '.TXT' ) (self.filesDeleted, self.filesAdded, self.filesEdited) = pickle.loads( changelistinfo ) self.changelistComment = 'vcodepickle: ' + os.path.basename(g_ZipFilename) # Load version2 data. try: ci2 = pickle.loads( self.theZipFile.read( g_ChangelistInfoFilename + 'v2.TXT' ) ) self.changelistComment = ci2.changelistComment except KeyError: pass # Load version3 data try: ci3 = pickle.loads( self.theZipFile.read( g_ChangelistInfoFilename + 'v3.TXT' ) ) self.relativeToDepotFileMap = ci3.relativeToDepotFileMap except KeyError: pass ## # Given a 'zip' instance, copy data from the 'name' to the # 'out' stream. def explode( out, zip, name ): zinfo = zip.getinfo(name) stringFileHeader = "PK\003\004" # magic number for file header structFileHeader = "<4s2B4HlLL2H" # 12 items, file header record, 30 bytes _FH_FILENAME_LENGTH = 10 _FH_EXTRA_FIELD_LENGTH = 11 filepos = zip.fp.tell() zip.fp.seek(zinfo.header_offset, 0) # Skip the file header: fheader = zip.fp.read(30) if fheader[0:4] != stringFileHeader: raise zipfile.BadZipfile, "Bad magic number for file header" fheader = struct.unpack(structFileHeader, fheader) fname = zip.fp.read(fheader[_FH_FILENAME_LENGTH]) if fheader[_FH_EXTRA_FIELD_LENGTH]: zip.fp.read(fheader[_FH_EXTRA_FIELD_LENGTH]) if fname != zinfo.orig_filename: raise zipfile.BadZipfile, 'File name in directory "%s" and header "%s" differ.' % ( zinfo.orig_filename, fname) if zinfo.compress_type == zipfile.ZIP_STORED: decoder = None elif zinfo.compress_type == zipfile.ZIP_DEFLATED: if not zlib: raise RuntimeError, "De-compression requires the (missing) zlib module" decoder = zlib.decompressobj(-zlib.MAX_WBITS) else: raise zipfile.BadZipfile,"Unsupported compression method %d for file %s" % (zinfo.compress_type, name) size = zinfo.compress_size while 1: data = zip.fp.read(min(size, 8192)) if not data: break size -= len(data) if decoder: data = decoder.decompress(data) out.write(data) if decoder: out.write(decoder.decompress('Z')) out.write(decoder.flush()) zip.fp.seek(filepos, 0) def WriteFileFromZip( theZipFile, srcFilename, sDestFilename ): # thebytes = theZipFile.read( srcFilename ) try: f = open( sDestFilename, 'wb') except: os.makedirs( os.path.dirname( sDestFilename ) ) f = open( sDestFilename, 'wb' ) explode( f, theZipFile, srcFilename ); # f.write( thebytes ) f.close() def PerforceToZipFilename( sP4Filename ): if sP4Filename[0] != '/' or sP4Filename[1] != '/': print "PerforceToZipFilename: invalid filename (%s)" % (sP4Filename) return sP4Filename[2:] def CheckFilesOpenedForEdit( thePickle, theDict ): ret = [] for k in theDict.keys(): sDepotFilename = thePickle.relativeToDepotFileMap[k] if p4helpers.GetClientFileAction( sDepotFilename ) != 'none': ret.append( sDepotFilename ) return ret def HandleNewTree( thePickle ): global g_FromTree, g_ToTree if g_FromTree == None: return print "-changetree is translating these files:\n" for k in thePickle.relativeToDepotFileMap.keys(): sFromFilename = thePickle.relativeToDepotFileMap[k] if sFromFilename[:len(g_FromTree)].lower() == g_FromTree.lower(): sToFilename = g_ToTree + sFromFilename[len(g_FromTree):] print sFromFilename + "\n\t-> " + sToFilename + "\n" thePickle.relativeToDepotFileMap[k] = sToFilename else: print "Used -changetree [%s] [%s], but file %s does not come from the source tree." % (g_FromTree, g_ToTree, sFilename) sys.exit( 1 ) # Translate the revision number to a changelist number for edited files. newEdited = {} for k in thePickle.filesEdited.keys(): thePickle.filesEdited[k] = "head" sDepotFilename = thePickle.relativeToDepotFileMap[k] if DoesFileExistInPerforce( sDepotFilename ): newEdited[k] = thePickle.filesEdited[k] else: AddQueuedWarning( "%s was edited in the old tree, but doesn't exist in the new tree. This was treated as an add instead." % sDepotFilename ) thePickle.filesAdded[k] = thePickle.filesEdited[k] thePickle.filesEdited = newEdited print "\n" def PromptForOpenZipFilename( sClientRoot, promptDir ): if not sys.modules.has_key( "wx" ): print >>sys.stderr, "Couldn't load wx, no UI available." sys.exit( 1 ) temp_app = wx.PySimpleApp() dialog = wx.FileDialog ( None, style = wx.OPEN ) dialog.SetMessage( "Select code pickle" ); dialog.SetDirectory(promptDir) dialog.SetWildcard( "VCodePickle Zips (*.zip)|*.zip|All Files (*.*)|*.*" ); if dialog.ShowModal() == wx.ID_OK: print 'Selected: %s\n' % (dialog.GetPath()) return dialog.GetPath() else: print 'Cancelled.' return None def DoRestore( sClientRoot, sZipFilename ): global g_bBinsOnly thePickle = VCodePickle() thePickle.Load( sZipFilename ) # First make sure they don't have any files open for edit that we want to mess with. print "Checking the code pickle...\n" openedForEdit = CheckFilesOpenedForEdit( thePickle, thePickle.filesAdded ) + CheckFilesOpenedForEdit( thePickle, thePickle.filesEdited ) if len( openedForEdit ) > 0 and g_bBinsOnly==False: print "You have one or more files open locally that are in the vcodepickle you are trying to restore:\n" for f in openedForEdit: print "\t" + f print "\nPlease revert the specified file(s) and retry the vcodepickle command you were doing." return changelistNum = None if ( g_bBinsOnly == False ): changelistNum = CreateChangelist( thePickle.changelistComment ) else: changelistNum = 'default' HandleNewTree( thePickle ) if g_bVerbose: print "Adding %d files..." % len( thePickle.filesAdded ) for k in thePickle.filesAdded.keys(): sDepotFilename = thePickle.relativeToDepotFileMap[k] if DoesFileExistInPerforce( sDepotFilename ): sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename )[0] AddQueuedWarning( "%s was added in the code pickle, but already exists in Perforce. We'll treat it as an edit." % sLocalClientFilename ); p4helpers.CheckPerforceReturn( 'p4 sync %s' % (sDepotFilename) ) p4helpers.CheckPerforceReturn( 'p4 edit -c %s %s' % (changelistNum, sDepotFilename) ) WriteFileFromZip( thePickle.theZipFile, k.replace('\\','/'), sLocalClientFilename ) else: # Note: We do the "p4 add" first because we don't yet know the local filename for this depot filename, # and there's no way to get it if the file doesn't already exist. # # Perforce will let you do a "p4 add" on a file that doesn't exist in your local tree yet, then you can # do a "p4 fstat" (p4helpers.GetClientFileInfo) on it to find its local filename. sCmd = 'p4 add -c %s %s' % (changelistNum, sDepotFilename) p4helpers.CheckPerforceReturn( sCmd ) # Get the local filename and copy from the zip file to that. sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename ) if sLocalClientFilename == None: AddQueuedWarning( "Unable to add %s. It probably isn't in your client spec." % sDepotFilename ) else: sLocalClientFilename = sLocalClientFilename[0] WriteFileFromZip( thePickle.theZipFile, k.replace('\\','/'), sLocalClientFilename ) if g_bVerbose: print "\t" + k if g_bVerbose: print "Deleting %d files..." % len( thePickle.filesDeleted ) for k in thePickle.filesDeleted.keys(): sDepotFilename = thePickle.relativeToDepotFileMap[k] p4helpers.CheckPerforceReturn( 'p4 delete -c %s %s ' % (changelistNum, sDepotFilename) ) if g_bVerbose: print "\t" + k if g_bVerbose: print "Editing %d files..." % len( thePickle.filesEdited ) for k in thePickle.filesEdited.keys(): revisionNumber = thePickle.filesEdited[k] sDepotFilename = thePickle.relativeToDepotFileMap[k] sLocalClientFilename = p4helpers.GetClientFileInfo( sDepotFilename )[0] # the zipfiles like forward slashes internalfilename = k.replace('\\','/') # Sync to the revision they had it at, edit, copy the file over, and sync to the latest so Perforce forces a resolve. p4helpers.CheckPerforceReturn( 'p4 sync %s#%s' % (sDepotFilename, revisionNumber) ) p4helpers.CheckPerforceReturn( 'p4 edit -c %s %s' % (changelistNum, sDepotFilename) ) WriteFileFromZip( thePickle.theZipFile, internalfilename, sLocalClientFilename ) p4helpers.CheckPerforceReturn( 'p4 sync %s' % sDepotFilename ) if g_bVerbose: print "\t%s#%s" % (k, revisionNumber) thePickle.theZipFile.close() PrintQueuedWarnings() if g_FromTree != None: print "\n" print " WARNING!!!!! -changetree edited the HEAD revision of the files in new tree." print " So if they have been edited since you checked them out for your" print " code pickle, then you must MANUALLY merge those changes into the new version." def SetupBinChangelists(): global g_Changelist clientInfo = p4helpers.GetClientInfo() pendingChanges = p4helpers.GetPendingChanges( clientInfo[ 'Client' ] ) for changeList in pendingChanges: if ( changeList[ 'desc' ].find( 'Visual Studio Auto Checkout' ) <> -1 ): g_Changelist.append( changeList[ 'change' ] ) def AutoFillChangelistArray(): global g_bBinsOnly global g_RunMode global g_bPickleAllChanges global g_Changelist if ( g_bBinsOnly and g_RunMode == RUNMODE_BACKUP ): SetupBinChangelists() elif g_bPickleAllChanges: clientInfo = p4helpers.GetClientInfo() pendingChanges = p4helpers.GetPendingChanges( clientInfo[ 'Client' ] ) for changeList in pendingChanges: g_Changelist.append( changeList['change'] ) g_Changelist.append( 'default' ) elif len( g_Changelist ) == 0 and g_RunMode == RUNMODE_BACKUP: g_Changelist.append( 'default' ) def CheckFileExcludedByPatterns( sFilename ): global g_IncludeGlobPatterns global g_ExcludeGlobPatterns bInclude = ( len( g_IncludeGlobPatterns ) == 0 ) # include by default for includePattern in g_IncludeGlobPatterns: if glob.fnmatch.fnmatch( sFilename, includePattern ): bInclude = True break if not bInclude: if g_bVerbose: print "skipping %s, doesn't match any include pattern" % sFilename return True for excludePattern in g_ExcludeGlobPatterns: if glob.fnmatch.fnmatch( sFilename, excludePattern ): if g_bVerbose: print "skipping %s matches exclude pattern %s" % ( sFilename, excludePattern ) return True return False def DoBackup( sClientRoot, sZipFilename, bAutoRevert ): global g_Changelist global g_ChangelistInfoFilename global g_ChangelistComment filesDeleted = {} filesAdded = {} filesEdited = {} # # Filter out all the files that aren't of the changelist we want. # openedFiles = p4helpers.ReadPerforceOutput( 'p4 -G opened' )[1] changelistfiles = [x for x in openedFiles if ( x['change'] in g_Changelist ) ] # Ok, there's something to do. Open the zip file. theZipFile = zipfile.ZipFile( sZipFilename, 'w', zipfile.ZIP_DEFLATED ) # # # relativeToDepotFileMap = {} for openedFile in changelistfiles: # Get the local client filename. depotFile = openedFile['depotFile'] # (//valvegames/main/src/etc....) x = p4helpers.GetClientFileInfo( depotFile ) localFilename = x[0] sFriendlyName = localFilename[ len(sClientRoot)+1: ] # ONLY for printing messages # Get the filename in the zipfile. We're just going to use the depot filename and make it palatable to the zipfile. # You could easily restore the depot filename from this filename, but for now we're just looking it up in relativeToDepotFileMap on the receiving end. sFilenameInZip = PerforceToZipFilename( depotFile ) # Check include and exclude patterns and skip this file if necessary. if CheckFileExcludedByPatterns( sFilenameInZip ): continue relativeToDepotFileMap[sFilenameInZip] = depotFile # Don't do anything if it's a delete. action = x[2] bCopy = True if action == 'delete': filesDeleted[sFilenameInZip] = 1 bCopy = False print "%s (DELETE)" % (sFriendlyName) else: if os.access( localFilename, os.F_OK ) == True: theZipFile.write( localFilename, sFilenameInZip ) else: print "\n**\n** WARNING: File '%s' is in the changelist but not on disk. It will not be in the zipfile.\n**\n" % sFriendlyName if action == 'add': filesAdded[sFilenameInZip] = 1 print "%s (ADD)" % (sFriendlyName) else: filesEdited[sFilenameInZip] = openedFile['rev'] print "%s#%s" % (sFriendlyName, openedFile['rev']) # Revert the file.. if bAutoRevert == True: p4helpers.CheckPerforceReturn( 'p4 revert ' + localFilename ) # Save the metadata about add/delete/edit and file revisions. data = pickle.dumps( [filesDeleted, filesAdded, filesEdited] ) theZipFile.writestr( g_ChangelistInfoFilename + '.TXT', data ) # Save v2 data. ci2 = CV2Data() ci2.changelistComment = g_ChangelistComment data = pickle.dumps( ci2 ) theZipFile.writestr( g_ChangelistInfoFilename + 'v2.TXT', data ) # Save v3 data. ci3 = CV3Data() ci3.relativeToDepotFileMap = relativeToDepotFileMap data = pickle.dumps( ci3 ) theZipFile.writestr( g_ChangelistInfoFilename + 'v3.TXT', data ) # # Write a human-readable changelist file. # theZipFile.writestr( 'ReadableChangelist.txt', g_ChangelistComment ) theZipFile.close() def DoEmptyPickle( sZipFilename ): global g_ChangelistComment filesDeleted = {} filesAdded = {} filesEdited = {} relativeToDepotFileMap = {} # Ok, there's something to do. Open the zip file. theZipFile = zipfile.ZipFile( sZipFilename, 'w', zipfile.ZIP_DEFLATED ) # Save the metadata about add/delete/edit and file revisions. data = pickle.dumps( [filesDeleted, filesAdded, filesEdited] ) theZipFile.writestr( g_ChangelistInfoFilename + '.TXT', data ) # Save v2 data. ci2 = CV2Data() ci2.changelistComment = g_ChangelistComment data = pickle.dumps( ci2 ) theZipFile.writestr( g_ChangelistInfoFilename + 'v2.TXT', data ) # Save v3 data. ci3 = CV3Data() ci3.relativeToDepotFileMap = relativeToDepotFileMap data = pickle.dumps( ci3 ) theZipFile.writestr( g_ChangelistInfoFilename + 'v3.TXT', data ) # # Write a human-readable changelist file. # theZipFile.writestr( 'ReadableChangelist.txt', g_ChangelistComment ) theZipFile.close() def DoDiff( sZipFilename, sDiffDir ): # Load the zipfile. thePickle = VCodePickle() thePickle.Load( sZipFilename ) sTempFilesDir = os.path.join( sDiffDir, "tempfiles" ) # Get rid of the old directory. if os.access( sDiffDir, os.F_OK ) == True: theinput = raw_input( "Warning: This will clear out %s. Proceed (y/N)? " % sDiffDir ) if theinput == '' or theinput.lower() == 'n': print "Exiting." sys.exit( 0 ) # Delete all the files. # This method is lame but if we use unlink() on a network path, you won't be able to delete them # next time around or even from that command line. You can delete them from an explorer window, but # not from the command prompt anymore. Strange. os.system( 'rd /s /q "%s"' % sDiffDir ) # Create the directories. os.makedirs( sTempFilesDir ) # Dump out the changelist description. f = open( os.path.join( sDiffDir, "changelist.txt" ), 'wt' ) f.write( thePickle.changelistComment ) f.close() # Dump out all the files. for sFilename in thePickle.filesEdited.keys(): # The zipfile utils like forward slashes. sInternalFilename = sFilename.replace('\\','/') # Write the file to tempfiles\basefilename sEditedFilename = os.path.join( sTempFilesDir, os.path.basename( sInternalFilename ) ) WriteFileFromZip( thePickle.theZipFile, sInternalFilename, sEditedFilename ) # Have Perforce write out the prior version. nFileRevision = int( thePickle.filesEdited[sFilename] ) sPriorFilename = os.path.join( sTempFilesDir, os.path.basename( sInternalFilename ) ) + '.prev' p4helpers.ReadPerforceOutput( 'p4 -G print -o %s %s#%d' % (sPriorFilename, thePickle.relativeToDepotFileMap[sFilename], nFileRevision) ) # Write out a batch file with the diff command. sBatchFilename = os.path.join( sDiffDir, sInternalFilename.replace( '/', '-' ) ) + '.bat' fp = open( sBatchFilename, 'wt' ) sPercent = '%' fp.write( """ @echo off setlocal """ ) fp.write( "set sPriorFilename=%s\n" % sPriorFilename ) fp.write( "set sEditedFilename=%s\n" % sEditedFilename ) fp.write( """ rem // ------------------------------------------------------------------------------------------------------------------------ // rem // First, check if a P4DIFF environment variable exists. rem // ------------------------------------------------------------------------------------------------------------------------ // set diffProg=%P4DIFF% if "%diffprog%" == "" ( goto CheckRegistry ) :DoDiff "%diffProg%" %sPriorFilename% %sEditedFilename% goto End rem // ------------------------------------------------------------------------------------------------------------------------ // rem // No P4DIFF? Ok, check for what P4Win has setup as its diff program. rem // The nasty for command there basically runs the reg command, does away with stderr output, pipes the output rem // to FINDSTR to strip out everything but the second line which contains the result we want. Then it takes the relevant rem // part of the end of that line, which is what we care about. rem // ------------------------------------------------------------------------------------------------------------------------ // :CheckRegistry for /F "tokens=2* delims=REG_SZ" %%a in ('reg query HKEY_CURRENT_USER\Software\Perforce\P4win\Options /v DiffApp 2^>nul ^| FINDSTR DiffApp') do set initialValue=%%a if "%initialValue%" == "" ( set diffProg=p4diff.exe ) else ( for /F "tokens=*" %%s in ("%initialValue%") do set diffProg=%%s ) goto DoDiff :End """ ) # Now the batch file references the diff program. Just add the arguments we care about. fp.close() print "Wrote %s" % sBatchFilename thePickle.theZipFile.close() # Open up that directory. os.system( 'start %s' % sDiffDir ) def main(): global g_bVerbose global g_RunMode global g_ZipFilename global g_bAutoRevert global g_bBinsOnly global g_bPickleAllChanges global g_Changelist global g_IncludeGlobPatterns global g_ExcludeGlobPatterns global g_ChangelistInfoFilename global g_ChangelistComment global g_DiffDir global g_bShowUI global g_DefaultFilename global g_bCreateDiff # # Parse out the command line. # try: opts, args = getopt.getopt( sys.argv[1:], "bdrf:c:ri:e:?q", [ "prompt", "diff", "diffdir=", "backup", "restore", "file=", "bins", "changelist=", "revert", "include=", "exclude=", "quiet", "help", "empty" ] ) except getopt.GetoptError, e: print "" print "Argument error: ", e print "" ShowUsage() sys.exit(1) bPrompt = False for o, a in opts: if o in ( "-?", "--help" ): ShowUsage() sys.exit(1) if o in ( "-q", "--quiet" ): g_bVerbose = False if o in ( "--prompt" ): bPrompt = True if o in ( "-b", "--backup" ): assert( g_RunMode == RUNMODE_NONE ) g_RunMode = RUNMODE_BACKUP if o in ( "-r", "--restore" ): assert( g_RunMode == RUNMODE_NONE ) g_RunMode = RUNMODE_RESTORE if o in ( "-e", "--empty" ): assert( g_RunMode == RUNMODE_NONE ) g_RunMode = RUNMODE_EMPTY if o in ( "-d", "--diff" ): g_bCreateDiff = True if o.lower() == "--diffdir" : g_DiffDir = a if o in ( "-f", "--file" ): g_ZipFilename = a if g_ZipFilename[-4:].lower() != '.zip': g_ZipFilename += '.zip' if o in ( "-c", "--changelist" ): if a in ( 'all', '*' ): g_bPickleAllChanges = True else: g_Changelist.append( a ) if o.lower() == "--bins": g_bBinsOnly = True if o in ( "--revert" ): g_bAutoRevert = True if o in ( "-i", "--include" ): g_IncludeGlobPatterns.append( a ) if o in ( "-e", "--exclude" ): g_ExcludeGlobPatterns.append( a ) if g_RunMode == RUNMODE_NONE and not g_bCreateDiff: ShowUsage() sys.exit( 1 ) if ( g_RunMode == RUNMODE_RESTORE or g_RunMode == RUNMODE_EMPTY ) and len( g_ZipFilename ) == 0 and not bPrompt: print >>sys.stderr, "\n*** file parameter is required in restore/empty mode ***\n\n\n" ShowUsage() sys.exit( 1 ) if len(g_Changelist) and g_bBinsOnly and g_RunMode == RUNMODE_BACKUP: print >>sys.stderr, "\n*** -c and --bins are mutally exclusive ***\n\n\n" ShowUsage() sys.exit( 1 ) if ( len( g_IncludeGlobPatterns ) or len( g_ExcludeGlobPatterns ) ) and g_RunMode != RUNMODE_BACKUP: print >>sys.stderr, "\n*** -i/-e apply only in backup mode ***\n\n\n" ShowUsage() sys.exit(1) g_Settings = LoadSettingsFile( g_SettingsFilename ) # Auto-fill g_Changelist if necessary. AutoFillChangelistArray() if g_RunMode == RUNMODE_BACKUP: # # Get the string for the changelist. # sDate = datetime.datetime.now().strftime("%Y-%m-%d__%Hh-%Mm-%Ss") if g_Changelist[0] == 'default': g_ChangelistComment = 'vcodepickle: ' + os.path.basename(g_ZipFilename) g_DefaultFilename = 'VCP_' + sDate + '.zip' else: g_ChangelistComment = GetChangelistComment( g_Changelist[0] ) partOfChangelistComment = GetReadableChangelistComment( g_ChangelistComment ) g_DefaultFilename = 'VCP_' + partOfChangelistComment + '_' + sDate + '.zip' # # Generate a default zip filename if they didn't specify one. # # Either we'll use a mixture of the current date and their changelist text # and if it was launched from a Perforce tool (used the -ui) parameter, # we'll let them specify where to save it (and default to \\fileserver\user\username\[the date and the changelist comment]) # if bPrompt == True: ShowUIForFilename() else: # # Non-ui mode. Just use what they entered on the command line or use the default if they didn't enter anything. # if len( g_ZipFilename ) == 0: SetZipFilename( g_DefaultFilename ) g_ClientInfo = p4helpers.GetClientInfo() # Get the root directory without a trailing slash. g_ClientRoot = g_ClientInfo['Root'] if g_ClientRoot[-1:] == '\\' or g_ClientRoot[-1:] == '/': g_ClientRoot = g_ClientRoot[:-1] if g_bVerbose: print "\n---- vcodepickle ----" print "zipfile : " + g_ZipFilename print "p4 client root : " + g_ClientRoot if g_bAutoRevert == True: print "-revert specified" print "-----------------------" print "" if g_RunMode == RUNMODE_BACKUP: DoBackup( g_ClientRoot, g_ZipFilename, g_bAutoRevert ) if g_RunMode == RUNMODE_EMPTY: DoEmptyPickle( g_ZipFilename ) elif g_RunMode == RUNMODE_RESTORE: if bPrompt: username = os.environ['username'] g_ZipFilename = PromptForOpenZipFilename( g_ClientRoot, DefaultPickleDirName(username) ) if g_ZipFilename != None: DoRestore( g_ClientRoot, g_ZipFilename ) else: DoRestore( g_ClientRoot, g_ZipFilename ) if g_bCreateDiff: print "\n\nPreparing diff...\n" username = os.environ['username'] if g_DiffDir == '': g_DiffDir = DefaultPickleDiffName(username) if bPrompt and ( g_RunMode != RUNMODE_BACKUP ): # If RUNMODE_BACKUP, then g_ZipFilename is already set to a valid thing. g_ZipFilename = PromptForOpenZipFilename( g_ClientRoot, DefaultPickleDirName(username) ) if g_ZipFilename != None: DoDiff( g_ZipFilename, g_DiffDir ) else: DoDiff( g_ZipFilename, g_DiffDir ) if __name__ == '__main__': main()